mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-18 16:47:48 +01:00
Compare commits
67 Commits
v2025.9.0-
...
v2025.9.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e6523f477 | ||
|
|
0c8d180928 | ||
|
|
e0f547b93f | ||
|
|
0a3506f81e | ||
|
|
e72c1e68e5 | ||
|
|
ef1ba9b834 | ||
|
|
846f4d9551 | ||
|
|
4780bfe41f | ||
|
|
d0d01b3897 | ||
|
|
fc1e8baa23 | ||
|
|
d35116c494 | ||
|
|
1d257b365b | ||
|
|
1076d57e8a | ||
|
|
1c93d5775f | ||
|
|
7b78fac24e | ||
|
|
6534b3f622 | ||
|
|
daba21fbca | ||
|
|
3b99ea1cad | ||
|
|
937d7aa72a | ||
|
|
5bf7278479 | ||
|
|
095af8cf4b | ||
|
|
e1c1ecc34d | ||
|
|
6e4c167bfd | ||
|
|
25d8357471 | ||
|
|
8e00693af3 | ||
|
|
079da67889 | ||
|
|
9ed3dacd28 | ||
|
|
ba6e64ef37 | ||
|
|
d7a68c2d53 | ||
|
|
e8e1d9246e | ||
|
|
a7574f2e5a | ||
|
|
69f9661813 | ||
|
|
302b0a4747 | ||
|
|
07f4696a2c | ||
|
|
2ddb1096df | ||
|
|
0149355d66 | ||
|
|
2e7749a883 | ||
|
|
cd0e8c0bc2 | ||
|
|
64e4e352a0 | ||
|
|
b512365f5a | ||
|
|
13c84e3fb6 | ||
|
|
8d1b17cac1 | ||
|
|
0c7034eefc | ||
|
|
3ec236462f | ||
|
|
1b5ac6fc89 | ||
|
|
d356bac135 | ||
|
|
8a80e7b833 | ||
|
|
a1ae065d37 | ||
|
|
79dd50474d | ||
|
|
dfa6f1c5b4 | ||
|
|
2edd33b6e3 | ||
|
|
8b851d4685 | ||
|
|
20e1b5c00e | ||
|
|
c4ab2965f7 | ||
|
|
0cad8f69e2 | ||
|
|
a8402824ed | ||
|
|
acf9458616 | ||
|
|
0a58f7dfc8 | ||
|
|
6e05d85ae4 | ||
|
|
a04db485de | ||
|
|
d7043e75d6 | ||
|
|
ec3e2e16a9 | ||
|
|
2bac610efe | ||
|
|
43a7132014 | ||
|
|
bddc6e35a0 | ||
|
|
0e98a3e498 | ||
|
|
17b6c945e6 |
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@@ -1,15 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: gschier
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: https://yaak.app/pricing
|
||||
|
||||
18
.github/workflows/ci-js.yml
vendored
18
.github/workflows/ci-js.yml
vendored
@@ -1,18 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop]
|
||||
|
||||
name: CI (JS)
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Lint/Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm test
|
||||
36
.github/workflows/ci-rust.yml
vendored
36
.github/workflows/ci-rust.yml
vendored
@@ -1,36 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop]
|
||||
paths:
|
||||
- src-tauri/**
|
||||
- .github/workflows/**
|
||||
|
||||
name: CI (Rust)
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: src-tauri
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Check/Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/cache@v3
|
||||
continue-on-error: false
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
- run: cargo check --all
|
||||
- run: cargo test --all
|
||||
31
.github/workflows/ci.yml
vendored
Normal file
31
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: Lint and Test
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Lint/Test
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: 'src-tauri'
|
||||
shared-key: ci
|
||||
cache-on-failure: true
|
||||
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- name: Run JS Tests
|
||||
run: npm test
|
||||
- name: Run Rust Tests
|
||||
run: cargo test --all
|
||||
working-directory: src-tauri
|
||||
89
.github/workflows/release.yml
vendored
89
.github/workflows/release.yml
vendored
@@ -16,15 +16,34 @@ jobs:
|
||||
- platform: 'macos-latest' # for Arm-based Macs (M1 and above).
|
||||
args: '--target aarch64-apple-darwin'
|
||||
yaak_arch: 'arm64'
|
||||
os: 'macos'
|
||||
targets: 'aarch64-apple-darwin'
|
||||
- platform: 'macos-latest' # for Intel-based Macs.
|
||||
args: '--target x86_64-apple-darwin'
|
||||
yaak_arch: 'x64'
|
||||
os: 'macos'
|
||||
targets: 'x86_64-apple-darwin'
|
||||
- platform: 'ubuntu-22.04'
|
||||
args: ''
|
||||
yaak_arch: 'x64'
|
||||
os: 'ubuntu'
|
||||
targets: ''
|
||||
- platform: 'ubuntu-22.04-arm'
|
||||
args: ''
|
||||
yaak_arch: 'arm64'
|
||||
os: 'ubuntu'
|
||||
targets: ''
|
||||
- platform: 'windows-latest'
|
||||
args: ''
|
||||
yaak_arch: 'x64'
|
||||
os: 'windows'
|
||||
targets: ''
|
||||
# Windows ARM64
|
||||
- platform: 'windows-latest'
|
||||
args: '--target aarch64-pc-windows-msvc'
|
||||
yaak_arch: 'arm64'
|
||||
os: 'windows'
|
||||
targets: 'aarch64-pc-windows-msvc'
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
@@ -33,35 +52,31 @@ jobs:
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
targets: ${{ matrix.targets }}
|
||||
|
||||
- uses: actions/cache@v3
|
||||
continue-on-error: false
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
src-tauri/target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
workspaces: 'src-tauri'
|
||||
shared-key: ci
|
||||
cache-on-failure: true
|
||||
|
||||
- name: install dependencies (Linux only)
|
||||
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
||||
if: matrix.os == 'ubuntu'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
|
||||
|
||||
- name: Install Protoc for plugin-runtime
|
||||
uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install trusted-signing-cli (Windows only)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
if: matrix.os == 'windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
@@ -73,23 +88,13 @@ jobs:
|
||||
echo $dir >> $env:GITHUB_PATH
|
||||
& $exe --version
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Protoc for plugin-runtime
|
||||
uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Some things (eg. WASM package) requires building before lint will work
|
||||
- name: Run bootstrap
|
||||
run: npm run bootstrap
|
||||
|
||||
- name: Run lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Run tests
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- name: Run JS Tests
|
||||
run: npm test
|
||||
- name: Run Rust Tests
|
||||
run: cargo test --all
|
||||
working-directory: src-tauri
|
||||
|
||||
- name: Set version
|
||||
run: npm run replace-version
|
||||
@@ -106,17 +111,17 @@ jobs:
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
# Apple signing stuff
|
||||
APPLE_CERTIFICATE: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_ID: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_TEAM_ID: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_TEAM_ID }}
|
||||
APPLE_CERTIFICATE: ${{ matrix.os == 'macos' && secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ matrix.os == 'macos' && secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_ID: ${{ matrix.os == 'macos' && secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ matrix.os == 'macos' && secrets.APPLE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ matrix.os == 'macos' && secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_TEAM_ID: ${{ matrix.os == 'macos' && secrets.APPLE_TEAM_ID }}
|
||||
|
||||
# Windows signing stuff
|
||||
AZURE_CLIENT_ID: ${{ matrix.platform == 'windows-latest' && secrets.AZURE_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ matrix.platform == 'windows-latest' && secrets.AZURE_CLIENT_SECRET }}
|
||||
AZURE_TENANT_ID: ${{ matrix.platform == 'windows-latest' && secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ matrix.os == 'windows' && secrets.AZURE_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ matrix.os == 'windows' && secrets.AZURE_CLIENT_SECRET }}
|
||||
AZURE_TENANT_ID: ${{ matrix.os == 'windows' && secrets.AZURE_TENANT_ID }}
|
||||
with:
|
||||
tagName: 'v__VERSION__'
|
||||
releaseName: 'Release __VERSION__'
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -15,6 +15,8 @@ dist-ssr
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/launch.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
out/
|
||||
.prettierrc.cjs
|
||||
@@ -1,8 +0,0 @@
|
||||
export default {
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"bracketSpacing": true
|
||||
}
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["biomejs.biome", "rust-lang.rust-analyzer", "bradlc.vscode-tailwindcss"]
|
||||
}
|
||||
26
.vscode/launch.json
vendored
Normal file
26
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Dev App",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "start"]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Build App",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "start"]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Bootstrap",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "bootstrap"]
|
||||
}
|
||||
]
|
||||
}
|
||||
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"editor.formatOnSave": true,
|
||||
"biome.enabled": true,
|
||||
"biome.lint.format.enable": true
|
||||
}
|
||||
@@ -60,3 +60,29 @@ _Note: For safety, development builds use a separate database location from prod
|
||||
# Example
|
||||
lezer-generator components/core/Editor/<LANG>/<LANG>.grammar > components/core/Editor/<LANG>/<LANG>.ts
|
||||
```
|
||||
|
||||
## Linting & Formatting
|
||||
|
||||
This repo uses Biome for linting and formatting (replacing ESLint + Prettier).
|
||||
|
||||
- Lint the entire repo:
|
||||
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
||||
|
||||
- Auto-fix lint issues where possible:
|
||||
|
||||
```sh
|
||||
npm run lint:fix
|
||||
```
|
||||
|
||||
- Format code:
|
||||
|
||||
```sh
|
||||
npm run format
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
|
||||
|
||||
<p align="center">
|
||||
<!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https://github.com/MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a> <a href="https://github.com/dharsanb"><img src="https://github.com/dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a> <a href="https://github.com/railwayapp"><img src="https://github.com/railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a> <a href="https://github.com/caseyamcl"><img src="https://github.com/caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a> <a href="https://github.com/andriyor"><img src="https://github.com/andriyor.png" width="80px" alt="User avatar: andriyor" /></a> <a href="https://github.com/"><img src="https://raw.githubusercontent.com/JamesIves/github-sponsors-readme-action/dev/.github/assets/placeholder.png" width="80px" alt="User avatar: " /></a> <!-- sponsors-premium -->
|
||||
<!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https://github.com/MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a> <a href="https://github.com/dharsanb"><img src="https://github.com/dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a> <a href="https://github.com/railwayapp"><img src="https://github.com/railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a> <a href="https://github.com/caseyamcl"><img src="https://github.com/caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a> <a href="https://github.com/"><img src="https://raw.githubusercontent.com/JamesIves/github-sponsors-readme-action/dev/.github/assets/placeholder.png" width="80px" alt="User avatar: " /></a> <!-- sponsors-premium -->
|
||||
</p>
|
||||
<p align="center">
|
||||
<!-- sponsors-base --><a href="https://github.com/seanwash"><img src="https://github.com/seanwash.png" width="50px" alt="User avatar: seanwash" /></a> <a href="https://github.com/jerath"><img src="https://github.com/jerath.png" width="50px" alt="User avatar: jerath" /></a> <a href="https://github.com/itsa-sh"><img src="https://github.com/itsa-sh.png" width="50px" alt="User avatar: itsa-sh" /></a> <a href="https://github.com/dmmulroy"><img src="https://github.com/dmmulroy.png" width="50px" alt="User avatar: dmmulroy" /></a> <a href="https://github.com/timcole"><img src="https://github.com/timcole.png" width="50px" alt="User avatar: timcole" /></a> <a href="https://github.com/VLZH"><img src="https://github.com/VLZH.png" width="50px" alt="User avatar: VLZH" /></a> <a href="https://github.com/terasaka2k"><img src="https://github.com/terasaka2k.png" width="50px" alt="User avatar: terasaka2k" /></a> <a href="https://github.com/majudhu"><img src="https://github.com/majudhu.png" width="50px" alt="User avatar: majudhu" /></a> <a href="https://github.com/axelrindle"><img src="https://github.com/axelrindle.png" width="50px" alt="User avatar: axelrindle" /></a> <a href="https://github.com/jirizverina"><img src="https://github.com/jirizverina.png" width="50px" alt="User avatar: jirizverina" /></a> <a href="https://github.com/chip-well"><img src="https://github.com/chip-well.png" width="50px" alt="User avatar: chip-well" /></a> <!-- sponsors-base -->
|
||||
<!-- sponsors-base --><a href="https://github.com/seanwash"><img src="https://github.com/seanwash.png" width="50px" alt="User avatar: seanwash" /></a> <a href="https://github.com/jerath"><img src="https://github.com/jerath.png" width="50px" alt="User avatar: jerath" /></a> <a href="https://github.com/itsa-sh"><img src="https://github.com/itsa-sh.png" width="50px" alt="User avatar: itsa-sh" /></a> <a href="https://github.com/dmmulroy"><img src="https://github.com/dmmulroy.png" width="50px" alt="User avatar: dmmulroy" /></a> <a href="https://github.com/timcole"><img src="https://github.com/timcole.png" width="50px" alt="User avatar: timcole" /></a> <a href="https://github.com/VLZH"><img src="https://github.com/VLZH.png" width="50px" alt="User avatar: VLZH" /></a> <a href="https://github.com/terasaka2k"><img src="https://github.com/terasaka2k.png" width="50px" alt="User avatar: terasaka2k" /></a> <a href="https://github.com/andriyor"><img src="https://github.com/andriyor.png" width="50px" alt="User avatar: andriyor" /></a> <a href="https://github.com/majudhu"><img src="https://github.com/majudhu.png" width="50px" alt="User avatar: majudhu" /></a> <a href="https://github.com/axelrindle"><img src="https://github.com/axelrindle.png" width="50px" alt="User avatar: axelrindle" /></a> <a href="https://github.com/jirizverina"><img src="https://github.com/jirizverina.png" width="50px" alt="User avatar: jirizverina" /></a> <a href="https://github.com/chip-well"><img src="https://github.com/chip-well.png" width="50px" alt="User avatar: chip-well" /></a> <a href="https://github.com/GRAYAH"><img src="https://github.com/GRAYAH.png" width="50px" alt="User avatar: GRAYAH" /></a> <!-- sponsors-base -->
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
51
biome.json
Normal file
51
biome.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.7/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",
|
||||
"!scripts",
|
||||
"!packages/plugin-runtime",
|
||||
"!packages/plugin-runtime-types",
|
||||
"!src-tauri",
|
||||
"!src-web/tailwind.config.cjs",
|
||||
"!src-web/postcss.config.cjs",
|
||||
"!src-web/vite.config.ts",
|
||||
"!src-web/routeTree.gen.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
const { defineConfig, globalIgnores } = require('eslint/config');
|
||||
|
||||
const { fixupConfigRules } = require('@eslint/compat');
|
||||
|
||||
const reactRefresh = require('eslint-plugin-react-refresh');
|
||||
const tsParser = require('@typescript-eslint/parser');
|
||||
const js = require('@eslint/js');
|
||||
|
||||
const { FlatCompat } = require('@eslint/eslintrc');
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
|
||||
module.exports = defineConfig([
|
||||
{
|
||||
extends: fixupConfigRules(
|
||||
compat.extends(
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:import/recommended',
|
||||
'plugin:jsx-a11y/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'eslint-config-prettier',
|
||||
),
|
||||
),
|
||||
|
||||
plugins: {
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
},
|
||||
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
|
||||
'import/resolver': {
|
||||
node: {
|
||||
paths: ['src-web'],
|
||||
extensions: ['.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
'react-refresh/only-export-components': 'error',
|
||||
'jsx-a11y/no-autofocus': 'off',
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{
|
||||
prefer: 'type-imports',
|
||||
disallowTypeAnnotations: true,
|
||||
fixStyle: 'separate-type-imports',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
globalIgnores([
|
||||
'scripts/**/*',
|
||||
'packages/plugin-runtime/**/*',
|
||||
'packages/plugin-runtime-types/**/*',
|
||||
'src-tauri/**/*',
|
||||
'src-web/tailwind.config.cjs',
|
||||
'src-web/vite.config.ts',
|
||||
]),
|
||||
globalIgnores([
|
||||
'**/node_modules/',
|
||||
'**/dist/',
|
||||
'**/build/',
|
||||
'**/.eslintrc.cjs',
|
||||
'**/.prettierrc.cjs',
|
||||
'src-web/postcss.config.cjs',
|
||||
'src-web/vite.config.ts',
|
||||
]),
|
||||
]);
|
||||
3356
package-lock.json
generated
3356
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@@ -28,6 +28,7 @@
|
||||
"plugins/importer-postman",
|
||||
"plugins/importer-postman-environment",
|
||||
"plugins/importer-yaak",
|
||||
"plugins/template-function-1password",
|
||||
"plugins/template-function-cookie",
|
||||
"plugins/template-function-ctx",
|
||||
"plugins/template-function-encode",
|
||||
@@ -37,10 +38,10 @@
|
||||
"plugins/template-function-prompt",
|
||||
"plugins/template-function-random",
|
||||
"plugins/template-function-regex",
|
||||
"plugins/template-function-request",
|
||||
"plugins/template-function-timestamp",
|
||||
"plugins/template-function-uuid",
|
||||
"plugins/template-function-xml",
|
||||
"plugins/template-function-request",
|
||||
"plugins/template-function-response",
|
||||
"plugins/themes-yaak",
|
||||
"src-tauri",
|
||||
@@ -69,36 +70,28 @@
|
||||
"icons:dev": "tauri icon src-tauri/icons/icon-dev.png --output src-tauri/icons/dev",
|
||||
"icons:release": "tauri icon src-tauri/icons/icon.png --output src-tauri/icons/release",
|
||||
"bootstrap": "run-s bootstrap:*",
|
||||
"bootstrap:install-wasm-pack": "node scripts/install-wasm-pack.cjs",
|
||||
"bootstrap:build": "npm run build",
|
||||
"bootstrap:vendor-node": "node scripts/vendor-node.cjs",
|
||||
"bootstrap:vendor-plugins": "node scripts/vendor-plugins.cjs",
|
||||
"bootstrap:vendor-protoc": "node scripts/vendor-protoc.cjs",
|
||||
"lint": "npm run --workspaces --if-present lint",
|
||||
"bootstrap:vendor": "npm run vendor",
|
||||
"vendor": "run-p vendor:*",
|
||||
"vendor:vendor-plugins": "node scripts/vendor-plugins.cjs",
|
||||
"vendor:vendor-protoc": "node scripts/vendor-protoc.cjs",
|
||||
"vendor:vendor-node": "node scripts/vendor-node.cjs",
|
||||
"lint": "run-p lint:*",
|
||||
"lint:biome": "biome lint",
|
||||
"lint:extra": "npm run --workspaces --if-present lint",
|
||||
"format": "biome format --write .",
|
||||
"replace-version": "node scripts/replace-version.cjs",
|
||||
"tauri": "tauri",
|
||||
"tauri-before-build": "npm run bootstrap && npm run --workspaces --if-present build",
|
||||
"tauri-before-build": "npm run bootstrap",
|
||||
"tauri-before-dev": "workspaces-run --parallel -- npm run --workspaces --if-present dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"jotai": "^2.12.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@tauri-apps/cli": "^2.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
||||
"@typescript-eslint/parser": "^8.27.0",
|
||||
"@yaakapp/cli": "^0.2.7",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"@biomejs/biome": "^2.3.7",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@yaakapp/cli": "^0.3.4",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4",
|
||||
"workspaces-run": "^1.0.2"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
export function debounce(fn: (...args: any[]) => void, delay = 500) {
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const result = function (...args: any[]) {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
const result = (...args: any[]) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => fn(...args), delay);
|
||||
};
|
||||
result.cancel = function () {
|
||||
result.cancel = () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
return result;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
export function formatSize(bytes: number): string {
|
||||
let num;
|
||||
let unit;
|
||||
let num: number;
|
||||
let unit: string;
|
||||
|
||||
if (bytes > 1000 * 1000 * 1000) {
|
||||
num = bytes / 1000 / 1000 / 1000;
|
||||
unit = 'GB';
|
||||
} else if (bytes > 1000 * 1000) {
|
||||
num = bytes / 1000 / 1000;
|
||||
unit = 'MB';
|
||||
} else if (bytes > 1000) {
|
||||
num = bytes / 1000;
|
||||
unit = 'KB';
|
||||
} else {
|
||||
num = bytes;
|
||||
unit = 'B';
|
||||
}
|
||||
if (bytes > 1000 * 1000 * 1000) {
|
||||
num = bytes / 1000 / 1000 / 1000;
|
||||
unit = 'GB';
|
||||
} else if (bytes > 1000 * 1000) {
|
||||
num = bytes / 1000 / 1000;
|
||||
unit = 'MB';
|
||||
} else if (bytes > 1000) {
|
||||
num = bytes / 1000;
|
||||
unit = 'KB';
|
||||
} else {
|
||||
num = bytes;
|
||||
unit = 'B';
|
||||
}
|
||||
|
||||
return `${Math.round(num * 10) / 10} ${unit}`;
|
||||
return `${Math.round(num * 10) / 10} ${unit}`;
|
||||
}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from './debounce';
|
||||
export * from './formatSize';
|
||||
export * from './templateFunction';
|
||||
|
||||
49
packages/common-lib/templateFunction.ts
Normal file
49
packages/common-lib/templateFunction.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type {
|
||||
CallTemplateFunctionArgs,
|
||||
JsonPrimitive,
|
||||
TemplateFunctionArg,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
|
||||
export function validateTemplateFunctionArgs(
|
||||
fnName: string,
|
||||
args: TemplateFunctionArg[],
|
||||
values: CallTemplateFunctionArgs['values'],
|
||||
): string | null {
|
||||
for (const arg of args) {
|
||||
if ('inputs' in arg && arg.inputs) {
|
||||
// Recurse down
|
||||
const err = validateTemplateFunctionArgs(fnName, arg.inputs, values);
|
||||
if (err) return err;
|
||||
}
|
||||
if (!('name' in arg)) continue;
|
||||
if (arg.optional) continue;
|
||||
if (arg.defaultValue != null) continue;
|
||||
if (arg.hidden) continue;
|
||||
if (values[arg.name] != null) continue;
|
||||
|
||||
return `Missing required argument "${arg.label || arg.name}" for template function ${fnName}()`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Recursively apply form input defaults to a set of values */
|
||||
export function applyFormInputDefaults(
|
||||
inputs: TemplateFunctionArg[],
|
||||
values: { [p: string]: JsonPrimitive | undefined },
|
||||
) {
|
||||
let newValues: { [p: string]: JsonPrimitive | undefined } = { ...values };
|
||||
for (const input of inputs) {
|
||||
if ('defaultValue' in input && values[input.name] === undefined) {
|
||||
newValues[input.name] = input.defaultValue;
|
||||
}
|
||||
if (input.type === 'checkbox' && values[input.name] === undefined) {
|
||||
newValues[input.name] = false;
|
||||
}
|
||||
// Recurse down to all child inputs
|
||||
if ('inputs' in input) {
|
||||
newValues = applyFormInputDefaults(input.inputs ?? [], newValues);
|
||||
}
|
||||
}
|
||||
return newValues;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PluginVersion } from "./gen_search.js";
|
||||
import type { PluginVersion } from "./gen_search";
|
||||
|
||||
export type PluginNameVersion = { name: string, version: string, };
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Environment, Folder, GrpcRequest, HttpRequest, HttpResponse, WebsocketRequest, Workspace } from "./gen_models.js";
|
||||
import type { JsonValue } from "./serde_json/JsonValue.js";
|
||||
import type { Environment, Folder, GrpcRequest, HttpRequest, HttpResponse, WebsocketRequest, Workspace } from "./gen_models";
|
||||
import type { JsonValue } from "./serde_json/JsonValue";
|
||||
|
||||
export type BootRequest = { dir: string, watch: boolean, };
|
||||
|
||||
@@ -450,7 +450,11 @@ export type TemplateFunction = { name: string, previewType?: TemplateFunctionPre
|
||||
* Also support alternative names. This is useful for not breaking existing
|
||||
* tags when changing the `name` property
|
||||
*/
|
||||
aliases?: Array<string>, args: Array<TemplateFunctionArg>, };
|
||||
aliases?: Array<string>, args: Array<TemplateFunctionArg>,
|
||||
/**
|
||||
* A list of arg names to show in the inline preview. If not provided, none will be shown (for privacy reasons).
|
||||
*/
|
||||
previewArgs?: Array<string>, };
|
||||
|
||||
/**
|
||||
* Similar to FormInput, but contains
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { applyFormInputDefaults, validateTemplateFunctionArgs } from '@yaakapp-internal/lib/templateFunction';
|
||||
import {
|
||||
BootRequest,
|
||||
DeleteKeyValueResponse,
|
||||
@@ -25,7 +26,7 @@ import { Context, PluginDefinition } from '@yaakapp/api';
|
||||
import console from 'node:console';
|
||||
import { type Stats, statSync, watch } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { applyDynamicFormInput, applyFormInputDefaults } from './common';
|
||||
import { applyDynamicFormInput } from './common';
|
||||
import { EventChannel } from './EventChannel';
|
||||
import { migrateTemplateFunctionSelectOptions } from './migrations';
|
||||
|
||||
@@ -334,15 +335,22 @@ export class PluginInstance {
|
||||
);
|
||||
} else if (typeof fn?.onRender === 'function') {
|
||||
const resolvedArgs = await applyDynamicFormInput(ctx, fn.args, payload.args);
|
||||
payload.args.values = applyFormInputDefaults(resolvedArgs, payload.args.values);
|
||||
try {
|
||||
const result = await fn.onRender(ctx, payload.args);
|
||||
const values = applyFormInputDefaults(resolvedArgs, payload.args.values);
|
||||
const error = validateTemplateFunctionArgs(fn.name, resolvedArgs, values);
|
||||
if (error && payload.args.purpose !== 'preview') {
|
||||
this.#sendPayload(
|
||||
context,
|
||||
{
|
||||
type: 'call_template_function_response',
|
||||
value: result ?? null,
|
||||
},
|
||||
{ type: 'call_template_function_response', value: null, error },
|
||||
replyId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fn.onRender(ctx, { ...payload.args, values });
|
||||
this.#sendPayload(
|
||||
context,
|
||||
{ type: 'call_template_function_response', value: result ?? null },
|
||||
replyId,
|
||||
);
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,29 +1,6 @@
|
||||
import {
|
||||
CallHttpAuthenticationActionArgs,
|
||||
CallTemplateFunctionArgs,
|
||||
JsonPrimitive,
|
||||
TemplateFunctionArg,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import { CallHttpAuthenticationActionArgs, CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
|
||||
import { Context, DynamicAuthenticationArg, DynamicTemplateFunctionArg } from '@yaakapp/api';
|
||||
|
||||
/** Recursively apply form input defaults to a set of values */
|
||||
export function applyFormInputDefaults(
|
||||
inputs: TemplateFunctionArg[],
|
||||
values: { [p: string]: JsonPrimitive | undefined },
|
||||
) {
|
||||
let newValues: { [p: string]: JsonPrimitive | undefined } = { ...values };
|
||||
for (const input of inputs) {
|
||||
if ('defaultValue' in input && values[input.name] === undefined) {
|
||||
newValues[input.name] = input.defaultValue;
|
||||
}
|
||||
// Recurse down to all child inputs
|
||||
if ('inputs' in input) {
|
||||
newValues = applyFormInputDefaults(input.inputs ?? [], newValues);
|
||||
}
|
||||
}
|
||||
return newValues;
|
||||
}
|
||||
|
||||
export async function applyDynamicFormInput(
|
||||
ctx: Context,
|
||||
args: DynamicTemplateFunctionArg[],
|
||||
@@ -48,7 +25,11 @@ export async function applyDynamicFormInput(
|
||||
...(typeof dynamic === 'function' ? await dynamic(ctx, callArgs as any) : undefined),
|
||||
};
|
||||
if ('inputs' in newArg && Array.isArray(newArg.inputs)) {
|
||||
newArg.inputs = await applyDynamicFormInput(ctx, newArg.inputs, callArgs as any);
|
||||
try {
|
||||
newArg.inputs = await applyDynamicFormInput(ctx, newArg.inputs, callArgs as any);
|
||||
} catch (e) {
|
||||
console.error('Failed to apply dynamic form input', e);
|
||||
}
|
||||
}
|
||||
resolvedArgs.push(newArg);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function convertToCurl(request: Partial<HttpRequest>) {
|
||||
if (urlParams.length > 0) {
|
||||
// Build url
|
||||
const [base, hash] = finalUrl.split('#');
|
||||
const separator = base!.includes('?') ? '&' : '?';
|
||||
const separator = base?.includes('?') ? '&' : '?';
|
||||
const queryString = urlParams
|
||||
.map((p) => `${encodeURIComponent(p.name)}=${encodeURIComponent(p.value)}`)
|
||||
.join('&');
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('exporter-curl', () => {
|
||||
{ name: 'c', value: 'ccc', enabled: false },
|
||||
],
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app?a=aaa&b=bbb'`].join(` \\n `));
|
||||
).toEqual([`curl 'https://yaak.app?a=aaa&b=bbb'`].join(' \\n '));
|
||||
});
|
||||
|
||||
test('Exports GET with params and hash', async () => {
|
||||
@@ -25,7 +25,7 @@ describe('exporter-curl', () => {
|
||||
{ name: 'c', value: 'ccc', enabled: false },
|
||||
],
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app/path?a=aaa&b=bbb#section'`].join(` \\n `));
|
||||
).toEqual([`curl 'https://yaak.app/path?a=aaa&b=bbb#section'`].join(' \\n '));
|
||||
});
|
||||
|
||||
test('Exports POST with url form data', async () => {
|
||||
@@ -43,7 +43,7 @@ describe('exporter-curl', () => {
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
[`curl -X POST 'https://yaak.app'`, `--data 'a=aaa'`, `--data 'b=bbb'`].join(` \\\n `),
|
||||
[`curl -X POST 'https://yaak.app'`, `--data 'a=aaa'`, `--data 'b=bbb'`].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('exporter-curl', () => {
|
||||
[
|
||||
`curl -X POST 'https://yaak.app'`,
|
||||
`--data '{"query":"{foo,bar}","variables":{"a":"aaa","b":"bbb"}}'`,
|
||||
].join(` \\\n `),
|
||||
].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('exporter-curl', () => {
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
[`curl -X POST 'https://yaak.app'`, `--data '{"query":"{foo,bar}"}'`].join(` \\\n `),
|
||||
[`curl -X POST 'https://yaak.app'`, `--data '{"query":"{foo,bar}"}'`].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -101,8 +101,8 @@ describe('exporter-curl', () => {
|
||||
`curl -X PUT 'https://yaak.app'`,
|
||||
`--form 'a=aaa'`,
|
||||
`--form 'b=bbb'`,
|
||||
`--form f=@/foo/bar.png;type=image/png`,
|
||||
].join(` \\\n `),
|
||||
'--form f=@/foo/bar.png;type=image/png',
|
||||
].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('exporter-curl', () => {
|
||||
`curl -X POST 'https://yaak.app'`,
|
||||
`--header 'Content-Type: application/json'`,
|
||||
`--data '{"foo":"bar\\'s"}'`,
|
||||
].join(` \\\n `),
|
||||
].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -142,7 +142,7 @@ describe('exporter-curl', () => {
|
||||
`curl -X POST 'https://yaak.app'`,
|
||||
`--header 'Content-Type: application/json'`,
|
||||
`--data '{"foo":"bar",\n"baz":"qux"}'`,
|
||||
].join(` \\\n `),
|
||||
].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -155,7 +155,7 @@ describe('exporter-curl', () => {
|
||||
{ name: 'c', value: 'ccc', enabled: false },
|
||||
],
|
||||
}),
|
||||
).toEqual([`curl ''`, `--header 'a: aaa'`, `--header 'b: bbb'`].join(` \\\n `));
|
||||
).toEqual([`curl ''`, `--header 'a: aaa'`, `--header 'b: bbb'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('Basic auth', async () => {
|
||||
@@ -168,7 +168,7 @@ describe('exporter-curl', () => {
|
||||
password: 'pass',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app'`, `--user 'user:pass'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app'`, `--user 'user:pass'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('Basic auth disabled', async () => {
|
||||
@@ -182,7 +182,7 @@ describe('exporter-curl', () => {
|
||||
password: 'pass',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('Broken basic auth', async () => {
|
||||
@@ -192,7 +192,7 @@ describe('exporter-curl', () => {
|
||||
authenticationType: 'basic',
|
||||
authentication: {},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app'`, `--user ':'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app'`, `--user ':'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('Digest auth', async () => {
|
||||
@@ -205,7 +205,7 @@ describe('exporter-curl', () => {
|
||||
password: 'pass',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app'`, `--digest --user 'user:pass'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app'`, `--digest --user 'user:pass'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('Bearer auth', async () => {
|
||||
@@ -217,7 +217,7 @@ describe('exporter-curl', () => {
|
||||
token: 'tok',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer tok'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer tok'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('Bearer auth with custom prefix', async () => {
|
||||
@@ -231,7 +231,7 @@ describe('exporter-curl', () => {
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
[`curl 'https://yaak.app'`, `--header 'Authorization: Token abc123'`].join(` \\\n `),
|
||||
[`curl 'https://yaak.app'`, `--header 'Authorization: Token abc123'`].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -245,7 +245,7 @@ describe('exporter-curl', () => {
|
||||
prefix: '',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: xyz789'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: xyz789'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('Broken bearer auth', async () => {
|
||||
@@ -258,7 +258,7 @@ describe('exporter-curl', () => {
|
||||
password: 'pass',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('AWS v4 auth', async () => {
|
||||
@@ -275,8 +275,8 @@ describe('exporter-curl', () => {
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
[`curl 'https://yaak.app'`, `--aws-sigv4 aws:amz:us-east-1:s3`, `--user 'ak:sk'`].join(
|
||||
` \\\n `,
|
||||
[`curl 'https://yaak.app'`, '--aws-sigv4 aws:amz:us-east-1:s3', `--user 'ak:sk'`].join(
|
||||
' \\\n ',
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -297,10 +297,10 @@ describe('exporter-curl', () => {
|
||||
).toEqual(
|
||||
[
|
||||
`curl 'https://yaak.app'`,
|
||||
`--aws-sigv4 aws:amz:us-east-1:s3`,
|
||||
'--aws-sigv4 aws:amz:us-east-1:s3',
|
||||
`--user 'ak:sk'`,
|
||||
`--header 'X-Amz-Security-Token: st'`,
|
||||
].join(` \\\n `),
|
||||
].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -315,7 +315,7 @@ describe('exporter-curl', () => {
|
||||
value: 'my-token',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'X-Header: my-token'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'X-Header: my-token'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('API key auth header query', async () => {
|
||||
@@ -330,7 +330,7 @@ describe('exporter-curl', () => {
|
||||
value: 'bar',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app?hi=there¶m=hi&foo=bar'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app?hi=there¶m=hi&foo=bar'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('API key auth header query with params', async () => {
|
||||
@@ -345,7 +345,7 @@ describe('exporter-curl', () => {
|
||||
value: 'bar',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app?param=hi&foo=bar'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app?param=hi&foo=bar'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('API key auth header default', async () => {
|
||||
@@ -357,7 +357,7 @@ describe('exporter-curl', () => {
|
||||
location: 'header',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'X-Api-Key: '`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'X-Api-Key: '`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('API key auth query', async () => {
|
||||
@@ -371,7 +371,7 @@ describe('exporter-curl', () => {
|
||||
value: 'bar-baz',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app?foo=bar-baz'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app?foo=bar-baz'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('API key auth query with existing', async () => {
|
||||
@@ -385,7 +385,7 @@ describe('exporter-curl', () => {
|
||||
value: 'there',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app?foo=bar&baz=qux&hi=there'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app?foo=bar&baz=qux&hi=there'`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('API key auth query default', async () => {
|
||||
@@ -397,7 +397,7 @@ describe('exporter-curl', () => {
|
||||
location: 'query',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app?foo=bar&baz=qux&token='`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app?foo=bar&baz=qux&token='`].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('Stale body data', async () => {
|
||||
@@ -409,6 +409,6 @@ describe('exporter-curl', () => {
|
||||
text: 'ignore me',
|
||||
},
|
||||
}),
|
||||
).toEqual([`curl 'https://yaak.app'`].join(` \\\n `));
|
||||
).toEqual([`curl 'https://yaak.app'`].join(' \\\n '));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GrpcRequest, PluginDefinition } from '@yaakapp/api';
|
||||
import path from 'node:path';
|
||||
import type { GrpcRequest, PluginDefinition } from '@yaakapp/api';
|
||||
|
||||
const NEWLINE = '\\\n ';
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ describe('exporter-curl', () => {
|
||||
},
|
||||
[],
|
||||
),
|
||||
).toEqual([`grpcurl yaak.app`].join(` \\\n `));
|
||||
).toEqual(['grpcurl yaak.app'].join(' \\\n '));
|
||||
});
|
||||
test('Basic metadata', async () => {
|
||||
expect(
|
||||
@@ -25,7 +25,7 @@ describe('exporter-curl', () => {
|
||||
},
|
||||
[],
|
||||
),
|
||||
).toEqual([`grpcurl -H 'aaa: AAA'`, `-H 'bbb: BBB'`, `yaak.app`].join(` \\\n `));
|
||||
).toEqual([`grpcurl -H 'aaa: AAA'`, `-H 'bbb: BBB'`, 'yaak.app'].join(' \\\n '));
|
||||
});
|
||||
test('Basic auth', async () => {
|
||||
expect(
|
||||
@@ -40,7 +40,7 @@ describe('exporter-curl', () => {
|
||||
},
|
||||
[],
|
||||
),
|
||||
).toEqual([`grpcurl -H 'Authorization: Basic dXNlcjpwYXNz'`, `yaak.app`].join(` \\\n `));
|
||||
).toEqual([`grpcurl -H 'Authorization: Basic dXNlcjpwYXNz'`, 'yaak.app'].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('API key auth', async () => {
|
||||
@@ -56,7 +56,7 @@ describe('exporter-curl', () => {
|
||||
},
|
||||
[],
|
||||
),
|
||||
).toEqual([`grpcurl -H 'X-Token: tok'`, `yaak.app`].join(` \\\n `));
|
||||
).toEqual([`grpcurl -H 'X-Token: tok'`, 'yaak.app'].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('API key auth', async () => {
|
||||
@@ -73,7 +73,7 @@ describe('exporter-curl', () => {
|
||||
},
|
||||
[],
|
||||
),
|
||||
).toEqual([`grpcurl`, `yaak.app?token=tok%201`].join(` \\\n `));
|
||||
).toEqual(['grpcurl', 'yaak.app?token=tok%201'].join(' \\\n '));
|
||||
});
|
||||
|
||||
test('Single proto file', async () => {
|
||||
@@ -82,8 +82,8 @@ describe('exporter-curl', () => {
|
||||
`grpcurl -import-path '/foo/bar'`,
|
||||
`-import-path '/foo'`,
|
||||
`-proto '/foo/bar/baz.proto'`,
|
||||
`yaak.app`,
|
||||
].join(` \\\n `),
|
||||
'yaak.app',
|
||||
].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
test('Multiple proto files, same dir', async () => {
|
||||
@@ -95,8 +95,8 @@ describe('exporter-curl', () => {
|
||||
`-import-path '/foo'`,
|
||||
`-proto '/foo/bar/aaa.proto'`,
|
||||
`-proto '/foo/bar/bbb.proto'`,
|
||||
`yaak.app`,
|
||||
].join(` \\\n `),
|
||||
'yaak.app',
|
||||
].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
test('Multiple proto files, different dir', async () => {
|
||||
@@ -110,18 +110,18 @@ describe('exporter-curl', () => {
|
||||
`-import-path '/xxx'`,
|
||||
`-proto '/aaa/bbb/ccc.proto'`,
|
||||
`-proto '/xxx/yyy/zzz.proto'`,
|
||||
`yaak.app`,
|
||||
].join(` \\\n `),
|
||||
'yaak.app',
|
||||
].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
test('Single include dir', async () => {
|
||||
expect(await convert({ url: 'https://yaak.app' }, ['/aaa/bbb'])).toEqual(
|
||||
[`grpcurl -import-path '/aaa/bbb'`, `yaak.app`].join(` \\\n `),
|
||||
[`grpcurl -import-path '/aaa/bbb'`, 'yaak.app'].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
test('Multiple include dir', async () => {
|
||||
expect(await convert({ url: 'https://yaak.app' }, ['/aaa/bbb', '/xxx/yyy'])).toEqual(
|
||||
[`grpcurl -import-path '/aaa/bbb'`, `-import-path '/xxx/yyy'`, `yaak.app`].join(` \\\n `),
|
||||
[`grpcurl -import-path '/aaa/bbb'`, `-import-path '/xxx/yyy'`, 'yaak.app'].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
test('Mixed proto and dirs', async () => {
|
||||
@@ -134,8 +134,8 @@ describe('exporter-curl', () => {
|
||||
`-import-path '/foo'`,
|
||||
`-import-path '/'`,
|
||||
`-proto '/foo/bar.proto'`,
|
||||
`yaak.app`,
|
||||
].join(` \\\n `),
|
||||
'yaak.app',
|
||||
].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
test('Sends data', async () => {
|
||||
@@ -152,8 +152,8 @@ describe('exporter-curl', () => {
|
||||
`grpcurl -import-path '/'`,
|
||||
`-proto '/foo.proto'`,
|
||||
`-d '{"foo":"bar","baz":1}'`,
|
||||
`yaak.app`,
|
||||
].join(` \\\n `),
|
||||
'yaak.app',
|
||||
].join(' \\\n '),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,15 @@ export const plugin: PluginDefinition = {
|
||||
name: 'key',
|
||||
label: 'Key',
|
||||
dynamic: (_ctx, { values }) => {
|
||||
return values.location === 'query' ? {
|
||||
label: 'Parameter Name',
|
||||
description: 'The name of the query parameter to add to the request',
|
||||
} : {
|
||||
label: 'Header Name',
|
||||
description: 'The name of the header to add to the request',
|
||||
};
|
||||
return values.location === 'query'
|
||||
? {
|
||||
label: 'Parameter Name',
|
||||
description: 'The name of the query parameter to add to the request',
|
||||
}
|
||||
: {
|
||||
label: 'Header Name',
|
||||
description: 'The name of the header to add to the request',
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -45,9 +47,8 @@ export const plugin: PluginDefinition = {
|
||||
|
||||
if (location === 'query') {
|
||||
return { setQueryParameters: [{ name: key, value }] };
|
||||
} else {
|
||||
return { setHeaders: [{ name: key, value }] };
|
||||
}
|
||||
return { setHeaders: [{ name: key, value }] };
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws4": "^1.13.2"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { CallHttpAuthenticationResponse } from '@yaakapp-internal/plugins';
|
||||
import type { PluginDefinition } from '@yaakapp/api';
|
||||
import aws4 from 'aws4';
|
||||
import type { Request } from 'aws4';
|
||||
import { URL } from 'node:url';
|
||||
import type { PluginDefinition } from '@yaakapp/api';
|
||||
import type { CallHttpAuthenticationResponse } from '@yaakapp-internal/plugins';
|
||||
import type { Request } from 'aws4';
|
||||
import aws4 from 'aws4';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
authentication: {
|
||||
@@ -64,8 +64,8 @@ export const plugin: PluginDefinition = {
|
||||
path: url.pathname + (url.search || ''),
|
||||
service: String(values.service || 'sts'),
|
||||
region: values.region ? String(values.region) : undefined,
|
||||
body: values.body ? String(values.body) : undefined,
|
||||
headers,
|
||||
doNotEncodePath: true,
|
||||
},
|
||||
{
|
||||
accessKeyId,
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,21 +5,24 @@ export const plugin: PluginDefinition = {
|
||||
name: 'basic',
|
||||
label: 'Basic Auth',
|
||||
shortLabel: 'Basic',
|
||||
args: [{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
label: 'Username',
|
||||
optional: true,
|
||||
}, {
|
||||
type: 'text',
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
optional: true,
|
||||
password: true,
|
||||
}],
|
||||
args: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
label: 'Username',
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
optional: true,
|
||||
password: true,
|
||||
},
|
||||
],
|
||||
async onApply(_ctx, { values }) {
|
||||
const { username, password } = values;
|
||||
const value = 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64');
|
||||
const value = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
||||
return { setHeaders: [{ name: 'Authorization', value }] };
|
||||
},
|
||||
},
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CallHttpAuthenticationRequest } from '@yaakapp-internal/plugins';
|
||||
import type { PluginDefinition } from '@yaakapp/api';
|
||||
import type { CallHttpAuthenticationRequest } from '@yaakapp-internal/plugins';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
authentication: {
|
||||
|
||||
@@ -7,7 +7,7 @@ const ctx = {} as Context;
|
||||
describe('auth-bearer', () => {
|
||||
test('No values', async () => {
|
||||
expect(
|
||||
await plugin.authentication!.onApply(ctx, {
|
||||
await plugin.authentication?.onApply(ctx, {
|
||||
values: {},
|
||||
headers: [],
|
||||
url: 'https://yaak.app',
|
||||
@@ -19,7 +19,7 @@ describe('auth-bearer', () => {
|
||||
|
||||
test('Only token', async () => {
|
||||
expect(
|
||||
await plugin.authentication!.onApply(ctx, {
|
||||
await plugin.authentication?.onApply(ctx, {
|
||||
values: { token: 'my-token' },
|
||||
headers: [],
|
||||
url: 'https://yaak.app',
|
||||
@@ -31,7 +31,7 @@ describe('auth-bearer', () => {
|
||||
|
||||
test('Only prefix', async () => {
|
||||
expect(
|
||||
await plugin.authentication!.onApply(ctx, {
|
||||
await plugin.authentication?.onApply(ctx, {
|
||||
values: { prefix: 'Hello' },
|
||||
headers: [],
|
||||
url: 'https://yaak.app',
|
||||
@@ -43,7 +43,7 @@ describe('auth-bearer', () => {
|
||||
|
||||
test('Prefix and token', async () => {
|
||||
expect(
|
||||
await plugin.authentication!.onApply(ctx, {
|
||||
await plugin.authentication?.onApply(ctx, {
|
||||
values: { prefix: 'Hello', token: 'my-token' },
|
||||
headers: [],
|
||||
url: 'https://yaak.app',
|
||||
@@ -55,7 +55,7 @@ describe('auth-bearer', () => {
|
||||
|
||||
test('Extra spaces', async () => {
|
||||
expect(
|
||||
await plugin.authentication!.onApply(ctx, {
|
||||
await plugin.authentication?.onApply(ctx, {
|
||||
values: { prefix: '\t Hello ', token: ' \nmy-token ' },
|
||||
headers: [],
|
||||
url: 'https://yaak.app',
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
|
||||
@@ -68,12 +68,11 @@ export const plugin: PluginDefinition = {
|
||||
label: 'Parameter Name',
|
||||
description: 'The name of the query parameter to add to the request',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
label: 'Header Name',
|
||||
description: 'The name of the header to add to the request',
|
||||
};
|
||||
}
|
||||
return {
|
||||
label: 'Header Name',
|
||||
description: 'The name of the header to add to the request',
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -110,12 +109,11 @@ export const plugin: PluginDefinition = {
|
||||
const paramName = String(values.name || 'token');
|
||||
const paramValue = String(values.value || '');
|
||||
return { setQueryParameters: [{ name: paramName, value: paramValue }] };
|
||||
} else {
|
||||
const headerPrefix = values.headerPrefix != null ? values.headerPrefix : 'Bearer';
|
||||
const headerName = String(values.name || 'Authorization');
|
||||
const headerValue = `${headerPrefix} ${token}`.trim();
|
||||
return { setHeaders: [{ name: headerName, value: headerValue }] };
|
||||
}
|
||||
const headerPrefix = values.headerPrefix != null ? values.headerPrefix : 'Bearer';
|
||||
const headerName = String(values.name || 'Authorization');
|
||||
const headerValue = `${headerPrefix} ${token}`.trim();
|
||||
return { setHeaders: [{ name: headerName, value: headerValue }] };
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"httpntlm": "^1.8.13"
|
||||
|
||||
@@ -8,6 +8,17 @@ export const plugin: PluginDefinition = {
|
||||
label: 'NTLM Auth',
|
||||
shortLabel: 'NTLM',
|
||||
args: [
|
||||
{
|
||||
type: 'banner',
|
||||
color: 'info',
|
||||
inputs: [
|
||||
{
|
||||
type: 'markdown',
|
||||
content:
|
||||
'NTLM is still in beta. Please submit any issues to [Feedback](https://yaak.app/feedback).',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"oauth-1.0a": "^2.2.6"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Context, GetHttpAuthenticationConfigRequest, PluginDefinition } from '@yaakapp/api';
|
||||
import crypto from 'node:crypto';
|
||||
import type { Context, GetHttpAuthenticationConfigRequest, PluginDefinition } from '@yaakapp/api';
|
||||
import OAuth from 'oauth-1.0a';
|
||||
|
||||
const signatures = {
|
||||
@@ -36,6 +36,17 @@ export const plugin: PluginDefinition = {
|
||||
label: 'OAuth 1.0',
|
||||
shortLabel: 'OAuth 1',
|
||||
args: [
|
||||
{
|
||||
type: 'banner',
|
||||
color: 'info',
|
||||
inputs: [
|
||||
{
|
||||
type: 'markdown',
|
||||
content:
|
||||
'OAuth 1.0 is still in beta. Please submit any issues to [Feedback](https://yaak.app/feedback).',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'signatureMethod',
|
||||
label: 'Signature Method',
|
||||
@@ -139,7 +150,9 @@ export const plugin: PluginDefinition = {
|
||||
for (const key of requestUrl.searchParams.keys()) {
|
||||
if (key.startsWith('oauth_')) continue;
|
||||
const all = requestUrl.searchParams.getAll(key);
|
||||
requestData.data[key] = all.length > 1 ? all : all[0]!;
|
||||
const first = all[0];
|
||||
if (first == null) continue;
|
||||
requestData.data[key] = all.length > 1 ? all : first;
|
||||
}
|
||||
|
||||
// (2) Manual oauth_* overrides
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Context, HttpRequest, HttpUrlParameter } from '@yaakapp/api';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import type { Context, HttpRequest, HttpUrlParameter } from '@yaakapp/api';
|
||||
import type { AccessTokenRawResponse } from './store';
|
||||
|
||||
export async function fetchAccessToken(
|
||||
@@ -39,15 +39,15 @@ export async function fetchAccessToken(
|
||||
],
|
||||
};
|
||||
|
||||
if (scope) httpRequest.body!.form.push({ name: 'scope', value: scope });
|
||||
if (audience) httpRequest.body!.form.push({ name: 'audience', value: audience });
|
||||
if (scope) httpRequest.body?.form.push({ name: 'scope', value: scope });
|
||||
if (audience) httpRequest.body?.form.push({ name: 'audience', value: audience });
|
||||
|
||||
if (credentialsInBody) {
|
||||
httpRequest.body!.form.push({ name: 'client_id', value: clientId });
|
||||
httpRequest.body!.form.push({ name: 'client_secret', value: clientSecret });
|
||||
httpRequest.body?.form.push({ name: 'client_id', value: clientId });
|
||||
httpRequest.body?.form.push({ name: 'client_secret', value: clientSecret });
|
||||
} else {
|
||||
const value = 'Basic ' + Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
||||
httpRequest.headers!.push({ name: 'Authorization', value });
|
||||
const value = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
|
||||
httpRequest.headers?.push({ name: 'Authorization', value });
|
||||
}
|
||||
|
||||
httpRequest.authenticationType = 'none'; // Don't inherit workspace auth
|
||||
@@ -58,12 +58,11 @@ export async function fetchAccessToken(
|
||||
const body = resp.bodyPath ? readFileSync(resp.bodyPath, 'utf8') : '';
|
||||
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(
|
||||
'Failed to fetch access token with status=' + resp.status + ' and body=' + body,
|
||||
);
|
||||
throw new Error(`Failed to fetch access token with status=${resp.status} and body=${body}`);
|
||||
}
|
||||
|
||||
let response;
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
let response: any;
|
||||
try {
|
||||
response = JSON.parse(body);
|
||||
} catch {
|
||||
@@ -71,7 +70,7 @@ export async function fetchAccessToken(
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
throw new Error('Failed to fetch access token with ' + response.error);
|
||||
throw new Error(`Failed to fetch access token with ${response.error}`);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Context, HttpRequest } from '@yaakapp/api';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import type { Context, HttpRequest } from '@yaakapp/api';
|
||||
import type { AccessToken, AccessTokenRawResponse, TokenStoreArgs } from './store';
|
||||
import { deleteToken, getToken, storeToken } from './store';
|
||||
import { isTokenExpired } from './util';
|
||||
@@ -58,23 +58,23 @@ export async function getOrRefreshAccessToken(
|
||||
],
|
||||
};
|
||||
|
||||
if (scope) httpRequest.body!.form.push({ name: 'scope', value: scope });
|
||||
if (scope) httpRequest.body?.form.push({ name: 'scope', value: scope });
|
||||
|
||||
if (credentialsInBody) {
|
||||
httpRequest.body!.form.push({ name: 'client_id', value: clientId });
|
||||
httpRequest.body!.form.push({ name: 'client_secret', value: clientSecret });
|
||||
httpRequest.body?.form.push({ name: 'client_id', value: clientId });
|
||||
httpRequest.body?.form.push({ name: 'client_secret', value: clientSecret });
|
||||
} else {
|
||||
const value = 'Basic ' + Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
||||
httpRequest.headers!.push({ name: 'Authorization', value });
|
||||
const value = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
|
||||
httpRequest.headers?.push({ name: 'Authorization', value });
|
||||
}
|
||||
|
||||
httpRequest.authenticationType = 'none'; // Don't inherit workspace auth
|
||||
const resp = await ctx.httpRequest.send({ httpRequest });
|
||||
|
||||
if (resp.status === 401) {
|
||||
// Bad refresh token, so we'll force it to fetch a fresh access token by deleting
|
||||
// and returning null;
|
||||
console.log('[oauth2] Unauthorized refresh_token request');
|
||||
if (resp.status >= 400 && resp.status < 500) {
|
||||
// Client errors (4xx) indicate the refresh token is invalid, expired, or revoked
|
||||
// Delete the token and return null to trigger a fresh authorization flow
|
||||
console.log('[oauth2] Refresh token request failed with client error, deleting token');
|
||||
await deleteToken(ctx, tokenArgs);
|
||||
return null;
|
||||
}
|
||||
@@ -84,12 +84,11 @@ export async function getOrRefreshAccessToken(
|
||||
console.log('[oauth2] Got refresh token response', resp.status);
|
||||
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(
|
||||
'Failed to refresh access token with status=' + resp.status + ' and body=' + body,
|
||||
);
|
||||
throw new Error(`Failed to refresh access token with status=${resp.status} and body=${body}`);
|
||||
}
|
||||
|
||||
let response;
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
let response: any;
|
||||
try {
|
||||
response = JSON.parse(body);
|
||||
} catch {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { createHash, randomBytes } from 'node:crypto';
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { fetchAccessToken } from '../fetchAccessToken';
|
||||
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
||||
import type { AccessToken, TokenStoreArgs } from '../store';
|
||||
@@ -84,7 +84,7 @@ export async function getAuthorizationCode(
|
||||
const authorizationUrlStr = authorizationUrl.toString();
|
||||
console.log('[oauth2] Authorizing', authorizationUrlStr);
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: none
|
||||
const code = await new Promise<string>(async (resolve, reject) => {
|
||||
let foundCode = false;
|
||||
const { close } = await ctx.window.openUrl({
|
||||
@@ -97,7 +97,7 @@ export async function getAuthorizationCode(
|
||||
}
|
||||
},
|
||||
async onNavigate({ url: urlStr }) {
|
||||
let code;
|
||||
let code: string | null;
|
||||
try {
|
||||
code = extractCode(urlStr, redirectUri);
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import type { AccessToken, AccessTokenRawResponse} from '../store';
|
||||
import { getDataDirKey , getToken, storeToken } from '../store';
|
||||
import type { AccessToken, AccessTokenRawResponse } from '../store';
|
||||
import { getDataDirKey, getToken, storeToken } from '../store';
|
||||
import { isTokenExpired } from '../util';
|
||||
|
||||
export async function getImplicit(
|
||||
@@ -56,7 +56,7 @@ export async function getImplicit(
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: none
|
||||
const newToken = await new Promise<AccessToken>(async (resolve, reject) => {
|
||||
let foundAccessToken = false;
|
||||
const authorizationUrlStr = authorizationUrl.toString();
|
||||
|
||||
@@ -27,7 +27,7 @@ const grantTypes: FormInputSelectOption[] = [
|
||||
{ label: 'Client Credentials', value: 'client_credentials' },
|
||||
];
|
||||
|
||||
const defaultGrantType = grantTypes[0]!.value;
|
||||
const defaultGrantType = grantTypes[0]?.value;
|
||||
|
||||
function hiddenIfNot(
|
||||
grantTypes: GrantType[],
|
||||
@@ -131,7 +131,6 @@ export const plugin: PluginDefinition = {
|
||||
type: 'select',
|
||||
name: 'grantType',
|
||||
label: 'Grant Type',
|
||||
hideLabel: true,
|
||||
defaultValue: defaultGrantType,
|
||||
options: grantTypes,
|
||||
},
|
||||
@@ -391,7 +390,7 @@ export const plugin: PluginDefinition = {
|
||||
credentialsInBody,
|
||||
});
|
||||
} else {
|
||||
throw new Error('Invalid grant type ' + grantType);
|
||||
throw new Error(`Invalid grant type ${grantType}`);
|
||||
}
|
||||
|
||||
const headerName = stringArg(values, 'headerName') || 'Authorization';
|
||||
@@ -406,7 +405,7 @@ function stringArgOrNull(
|
||||
name: string,
|
||||
): string | null {
|
||||
const arg = values[name];
|
||||
if (arg == null || arg == '') return null;
|
||||
if (arg == null || arg === '') return null;
|
||||
return `${arg}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { createHash } from 'node:crypto';
|
||||
import type { Context } from '@yaakapp/api';
|
||||
|
||||
export async function storeToken(
|
||||
ctx: Context,
|
||||
|
||||
@@ -50,7 +50,7 @@ export function extractCode(urlStr: string, redirectUri: string | null): string
|
||||
export function urlMatchesRedirect(url: URL, redirectUrl: string | null): boolean {
|
||||
if (!redirectUrl) return true;
|
||||
|
||||
let redirect;
|
||||
let redirect: URL;
|
||||
try {
|
||||
redirect = new URL(redirectUrl);
|
||||
} catch {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { extractCode } from '../src/util';
|
||||
|
||||
describe('extractCode', () => {
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonpath-plus": "^10.3.0"
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.9.8",
|
||||
|
||||
@@ -7,16 +7,15 @@ export const plugin: PluginDefinition = {
|
||||
name: 'XPath',
|
||||
description: 'Filter XPath',
|
||||
onFilter(_ctx, args) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
const doc: any = new DOMParser().parseFromString(args.payload, 'text/xml');
|
||||
try {
|
||||
const result = xpath.select(args.filter, doc, false);
|
||||
if (Array.isArray(result)) {
|
||||
return { content: result.map((r) => String(r)).join('\n') };
|
||||
} else {
|
||||
// Not sure what cases this happens in (?)
|
||||
return { content: String(result) };
|
||||
}
|
||||
// Not sure what cases this happens in (?)
|
||||
return { content: String(result) };
|
||||
} catch (err) {
|
||||
return { content: '', error: `Invalid filter: ${err}` };
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import type { Context, Environment, Folder, HttpRequest, HttpUrlParameter, PluginDefinition, Workspace } from '@yaakapp/api';
|
||||
import type {
|
||||
Context,
|
||||
Environment,
|
||||
Folder,
|
||||
HttpRequest,
|
||||
HttpUrlParameter,
|
||||
PluginDefinition,
|
||||
Workspace,
|
||||
} from '@yaakapp/api';
|
||||
import type { ControlOperator, ParseEntry } from 'shell-quote';
|
||||
import { parse } from 'shell-quote';
|
||||
|
||||
@@ -28,7 +36,7 @@ const SUPPORTED_FLAGS = [
|
||||
['url-query'],
|
||||
['user', 'u'], // Authentication
|
||||
DATA_FLAGS,
|
||||
].flatMap((v) => v);
|
||||
].flat();
|
||||
|
||||
const BOOLEAN_FLAGS = ['G', 'get', 'digest'];
|
||||
|
||||
@@ -41,12 +49,40 @@ export const plugin: PluginDefinition = {
|
||||
name: 'cURL',
|
||||
description: 'Import cURL commands',
|
||||
onImport(_ctx: Context, args: { text: string }) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
return convertCurl(args.text) as any;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes escape sequences in shell $'...' strings
|
||||
* Handles Unicode escape sequences (\uXXXX) and common escape codes
|
||||
*/
|
||||
function decodeShellString(str: string): string {
|
||||
return str
|
||||
.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
||||
.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\\r/g, '\r')
|
||||
.replace(/\\t/g, '\t')
|
||||
.replace(/\\'/g, "'")
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\\\/g, '\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string might contain escape sequences that need decoding
|
||||
* If so, decodes them; otherwise returns the string as-is
|
||||
*/
|
||||
function maybeDecodeEscapeSequences(str: string): string {
|
||||
// Check if the string contains escape sequences that shell-quote might not handle
|
||||
if (str.includes('\\u') || str.includes('\\x')) {
|
||||
return decodeShellString(str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function convertCurl(rawData: string) {
|
||||
if (!rawData.match(/^\s*curl /)) {
|
||||
return null;
|
||||
@@ -78,9 +114,11 @@ export function convertCurl(rawData: string) {
|
||||
for (const parseEntry of normalizedParseEntries) {
|
||||
if (typeof parseEntry === 'string') {
|
||||
if (parseEntry.startsWith('$')) {
|
||||
currentCommand.push(parseEntry.slice(1));
|
||||
// Handle $'...' strings from shell-quote - decode escape sequences
|
||||
currentCommand.push(decodeShellString(parseEntry.slice(1)));
|
||||
} else {
|
||||
currentCommand.push(parseEntry);
|
||||
// Decode escape sequences that shell-quote might not handle
|
||||
currentCommand.push(maybeDecodeEscapeSequences(parseEntry));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -100,7 +138,7 @@ export function convertCurl(rawData: string) {
|
||||
|
||||
if (op?.startsWith('$')) {
|
||||
// Handle the case where literal like -H $'Header: \'Some Quoted Thing\''
|
||||
const str = op.slice(2, op.length - 1).replace(/\\'/g, '\'');
|
||||
const str = decodeShellString(op.slice(2, op.length - 1));
|
||||
|
||||
currentCommand.push(str);
|
||||
continue;
|
||||
@@ -153,7 +191,7 @@ function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let value;
|
||||
let value: string | boolean;
|
||||
const nextEntry = parseEntries[i + 1];
|
||||
const hasValue = !BOOLEAN_FLAGS.includes(name);
|
||||
if (isSingleDash && name.length > 1) {
|
||||
@@ -169,7 +207,7 @@ function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
|
||||
}
|
||||
|
||||
flagsByName[name] = flagsByName[name] || [];
|
||||
flagsByName[name]!.push(value);
|
||||
flagsByName[name]?.push(value);
|
||||
} else if (parseEntry) {
|
||||
singletons.push(parseEntry);
|
||||
}
|
||||
@@ -184,7 +222,11 @@ function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
|
||||
const urlParameters: HttpUrlParameter[] =
|
||||
search?.split('&').map((p) => {
|
||||
const v = splitOnce(p, '=');
|
||||
return { name: decodeURIComponent(v[0] ?? ''), value: decodeURIComponent(v[1] ?? ''), enabled: true };
|
||||
return {
|
||||
name: decodeURIComponent(v[0] ?? ''),
|
||||
value: decodeURIComponent(v[1] ?? ''),
|
||||
enabled: true,
|
||||
};
|
||||
}) ?? [];
|
||||
|
||||
const url = baseUrl ?? urlArg;
|
||||
@@ -209,15 +251,15 @@ function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
|
||||
const authenticationType = username ? (isDigest ? 'digest' : 'basic') : null;
|
||||
const authentication = username
|
||||
? {
|
||||
username: username.trim(),
|
||||
password: (password ?? '').trim(),
|
||||
}
|
||||
username: username.trim(),
|
||||
password: (password ?? '').trim(),
|
||||
}
|
||||
: {};
|
||||
|
||||
// Headers
|
||||
const headers = [
|
||||
...((flagsByName['header'] as string[] | undefined) || []),
|
||||
...((flagsByName['H'] as string[] | undefined) || []),
|
||||
...((flagsByName.header as string[] | undefined) || []),
|
||||
...((flagsByName.H as string[] | undefined) || []),
|
||||
].map((header) => {
|
||||
const [name, value] = header.split(/:(.*)$/);
|
||||
// remove final colon from header name if present
|
||||
@@ -237,8 +279,8 @@ function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
|
||||
|
||||
// Cookies
|
||||
const cookieHeaderValue = [
|
||||
...((flagsByName['cookie'] as string[] | undefined) || []),
|
||||
...((flagsByName['b'] as string[] | undefined) || []),
|
||||
...((flagsByName.cookie as string[] | undefined) || []),
|
||||
...((flagsByName.b as string[] | undefined) || []),
|
||||
]
|
||||
.map((str) => {
|
||||
const name = str.split('=', 1)[0];
|
||||
@@ -269,8 +311,8 @@ function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
|
||||
|
||||
// Body (Multipart Form Data)
|
||||
const formDataParams = [
|
||||
...((flagsByName['form'] as string[] | undefined) || []),
|
||||
...((flagsByName['F'] as string[] | undefined) || []),
|
||||
...((flagsByName.form as string[] | undefined) || []),
|
||||
...((flagsByName.F as string[] | undefined) || []),
|
||||
].map((str) => {
|
||||
const parts = str.split('=');
|
||||
const name = parts[0] ?? '';
|
||||
@@ -281,9 +323,9 @@ function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
|
||||
};
|
||||
|
||||
if (value.indexOf('@') === 0) {
|
||||
item['file'] = value.slice(1);
|
||||
item.file = value.slice(1);
|
||||
} else {
|
||||
item['value'] = value;
|
||||
item.value = value;
|
||||
}
|
||||
|
||||
return item;
|
||||
@@ -384,7 +426,7 @@ function pairsToDataParameters(keyedPairs: FlagsByName): DataParameter[] {
|
||||
|
||||
for (const p of pairs) {
|
||||
if (typeof p !== 'string') continue;
|
||||
const params = p.split("&");
|
||||
const params = p.split('&');
|
||||
for (const param of params) {
|
||||
const [name, value] = splitOnce(param, '=');
|
||||
if (param.startsWith('@')) {
|
||||
@@ -398,7 +440,7 @@ function pairsToDataParameters(keyedPairs: FlagsByName): DataParameter[] {
|
||||
} else {
|
||||
dataParameters.push({
|
||||
name: name ?? '',
|
||||
value: flagName === 'data-urlencode' ? encodeURIComponent(value ?? '') : value ?? '',
|
||||
value: flagName === 'data-urlencode' ? encodeURIComponent(value ?? '') : (value ?? ''),
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
@@ -415,8 +457,8 @@ const getPairValue = <T extends string | boolean>(
|
||||
names: string[],
|
||||
) => {
|
||||
for (const name of names) {
|
||||
if (pairsByName[name] && pairsByName[name]!.length) {
|
||||
return pairsByName[name]![0] as T;
|
||||
if (pairsByName[name]?.length) {
|
||||
return pairsByName[name]?.[0] as T;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -374,7 +374,7 @@ describe('importer-curl', () => {
|
||||
httpRequests: [
|
||||
baseRequest({
|
||||
url: 'https://yaak.app',
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
bodyType: 'application/x-www-form-urlencoded',
|
||||
body: {
|
||||
form: [{ name: 'foo', value: 'bar=baz', enabled: true }],
|
||||
@@ -391,6 +391,56 @@ describe('importer-curl', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Imports data with Unicode escape sequences', () => {
|
||||
expect(
|
||||
convertCurl(
|
||||
`curl 'https://yaak.app' -H 'Content-Type: application/json' --data-raw $'{"query":"SearchQueryInput\\u0021"}' -X POST`,
|
||||
),
|
||||
).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
baseRequest({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
headers: [{ name: 'Content-Type', value: 'application/json', enabled: true }],
|
||||
bodyType: 'application/json',
|
||||
body: { text: '{"query":"SearchQueryInput!"}' },
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Imports data with multiple escape sequences', () => {
|
||||
expect(
|
||||
convertCurl(
|
||||
`curl 'https://yaak.app' --data-raw $'Line1\\nLine2\\tTab\\u0021Exclamation' -X POST`,
|
||||
),
|
||||
).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
baseRequest({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
bodyType: 'application/x-www-form-urlencoded',
|
||||
body: {
|
||||
form: [{ name: 'Line1\nLine2\tTab!Exclamation', value: '', enabled: true }],
|
||||
},
|
||||
headers: [
|
||||
{
|
||||
enabled: true,
|
||||
name: 'Content-Type',
|
||||
value: 'application/x-www-form-urlencoded',
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const idCount: Partial<Record<string, number>> = {};
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -16,28 +16,30 @@ export function convertId(id: string): string {
|
||||
export function deleteUndefinedAttrs<T>(obj: T): T {
|
||||
if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(deleteUndefinedAttrs) as T;
|
||||
} else if (typeof obj === 'object' && obj != null) {
|
||||
}
|
||||
if (typeof obj === 'object' && obj != null) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj)
|
||||
.filter(([, v]) => v !== undefined)
|
||||
.map(([k, v]) => [k, deleteUndefinedAttrs(v)]),
|
||||
) as T;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** Recursively render all nested object properties */
|
||||
export function convertTemplateSyntax<T>(obj: T): T {
|
||||
if (typeof obj === 'string') {
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Yaak template syntax
|
||||
return obj.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}') as T;
|
||||
} else if (Array.isArray(obj) && obj != null) {
|
||||
}
|
||||
if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(convertTemplateSyntax) as T;
|
||||
} else if (typeof obj === 'object' && obj != null) {
|
||||
}
|
||||
if (typeof obj === 'object' && obj != null) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([k, v]) => [k, convertTemplateSyntax(v)]),
|
||||
) as T;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// biome-ignore-all lint/suspicious/noExplicitAny: too flexible for strict types
|
||||
import type { PartialImportResources } from '@yaakapp/api';
|
||||
import { convertId, convertTemplateSyntax, isJSObject } from './common';
|
||||
|
||||
@@ -187,9 +187,9 @@ function importFolder(f: any, workspaceId: string): PartialImportResources['fold
|
||||
function importEnvironment(
|
||||
e: any,
|
||||
workspaceId: string,
|
||||
isParent?: boolean,
|
||||
isParentOg?: boolean,
|
||||
): PartialImportResources['environments'][0] {
|
||||
isParent ??= e.parentId === workspaceId;
|
||||
const isParent = isParentOg ?? e.parentId === workspaceId;
|
||||
return {
|
||||
id: convertId(e._id),
|
||||
createdAt: e.created ? new Date(e.created).toISOString().replace('Z', '') : undefined,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// biome-ignore-all lint/suspicious/noExplicitAny: too flexible for strict types
|
||||
import type { PartialImportResources } from '@yaakapp/api';
|
||||
import { convertId, convertTemplateSyntax, isJSObject } from './common';
|
||||
|
||||
@@ -249,7 +249,7 @@ function importFolder(
|
||||
let environment: PartialImportResources['environments'][0] | null = null;
|
||||
if (Object.keys(f.environment ?? {}).length > 0) {
|
||||
environment = {
|
||||
id: convertId(id + 'folder'),
|
||||
id: convertId(`${id}folder`),
|
||||
createdAt: created ? new Date(created).toISOString().replace('Z', '') : undefined,
|
||||
updatedAt: updated ? new Date(updated).toISOString().replace('Z', '') : undefined,
|
||||
workspaceId: convertId(workspaceId),
|
||||
|
||||
@@ -13,9 +13,12 @@ describe('importer-yaak', () => {
|
||||
continue;
|
||||
}
|
||||
|
||||
test('Imports ' + fixture, () => {
|
||||
test(`Imports ${fixture}`, () => {
|
||||
const contents = fs.readFileSync(path.join(p, fixture), 'utf-8');
|
||||
const expected = fs.readFileSync(path.join(p, fixture.replace(/.input\..*/, '.output.json')), 'utf-8');
|
||||
const expected = fs.readFileSync(
|
||||
path.join(p, fixture.replace(/.input\..*/, '.output.json')),
|
||||
'utf-8',
|
||||
);
|
||||
const result = convertInsomnia(contents);
|
||||
// console.log(JSON.stringify(result, null, 2))
|
||||
expect(result).toEqual(parseJsonOrYaml(expected));
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -14,10 +14,11 @@ export const plugin: PluginDefinition = {
|
||||
};
|
||||
|
||||
export async function convertOpenApi(contents: string): Promise<ImportPluginResponse | undefined> {
|
||||
let postmanCollection;
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
let postmanCollection: any;
|
||||
try {
|
||||
postmanCollection = await new Promise((resolve, reject) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
convert({ type: 'string', data: contents }, {}, (err, result: any) => {
|
||||
if (err != null) reject(err);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('importer-openapi', () => {
|
||||
});
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
test('Imports ' + fixture, async () => {
|
||||
test(`Imports ${fixture}`, async () => {
|
||||
const contents = fs.readFileSync(path.join(p, fixture), 'utf-8');
|
||||
const imported = await convertOpenApi(contents);
|
||||
expect(imported?.resources.workspaces).toEqual([
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,39 +93,37 @@ function toRecord<T>(value: Record<string, T> | unknown): Record<string, T> {
|
||||
|
||||
function toArray<T>(value: unknown): T[] {
|
||||
if (Object.prototype.toString.call(value) === '[object Array]') return value as T[];
|
||||
else return [] as T[];
|
||||
return [] as T[];
|
||||
}
|
||||
|
||||
/** Recursively render all nested object properties */
|
||||
function convertTemplateSyntax<T>(obj: T): T {
|
||||
if (typeof obj === 'string') {
|
||||
return obj.replace(
|
||||
/{{\s*(_\.)?([^}]*)\s*}}/g,
|
||||
(_m, _dot, expr) => '${[' + expr.trim() + ']}',
|
||||
) as T;
|
||||
} else if (Array.isArray(obj) && obj != null) {
|
||||
return obj.replace(/{{\s*(_\.)?([^}]*)\s*}}/g, (_m, _dot, expr) => `\${[${expr.trim()}]}`) as T;
|
||||
}
|
||||
if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(convertTemplateSyntax) as T;
|
||||
} else if (typeof obj === 'object' && obj != null) {
|
||||
}
|
||||
if (typeof obj === 'object' && obj != null) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj as Record<string, unknown>).map(([k, v]) => [k, convertTemplateSyntax(v)]),
|
||||
) as T;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function deleteUndefinedAttrs<T>(obj: T): T {
|
||||
if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(deleteUndefinedAttrs) as T;
|
||||
} else if (typeof obj === 'object' && obj != null) {
|
||||
}
|
||||
if (typeof obj === 'object' && obj != null) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj as Record<string, unknown>)
|
||||
.filter(([, v]) => v !== undefined)
|
||||
.map(([k, v]) => [k, deleteUndefinedAttrs(v)]),
|
||||
) as T;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
const idCount: Partial<Record<string, number>> = {};
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('importer-postman-environment', () => {
|
||||
continue;
|
||||
}
|
||||
|
||||
test('Imports ' + fixture, () => {
|
||||
test(`Imports ${fixture}`, () => {
|
||||
const contents = fs.readFileSync(path.join(p, fixture), 'utf-8');
|
||||
const expected = fs.readFileSync(path.join(p, fixture.replace('.input', '.output')), 'utf-8');
|
||||
const result = convertPostmanEnvironment(contents);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ function convertUrl(rawUrl: string | unknown): Pick<HttpRequest, 'url' | 'urlPar
|
||||
if ('variable' in url && Array.isArray(url.variable) && url.variable.length > 0) {
|
||||
for (const v of url.variable) {
|
||||
params.push({
|
||||
name: ':' + (v.key ?? ''),
|
||||
name: `:${v.key ?? ''}`,
|
||||
value: v.value ?? '',
|
||||
enabled: !v.disabled,
|
||||
});
|
||||
@@ -414,7 +414,8 @@ function importBody(rawBody: unknown): Pick<HttpRequest, 'body' | 'bodyType' | '
|
||||
),
|
||||
},
|
||||
};
|
||||
} else if (body.mode === 'urlencoded') {
|
||||
}
|
||||
if (body.mode === 'urlencoded') {
|
||||
return {
|
||||
headers: [
|
||||
{
|
||||
@@ -432,7 +433,8 @@ function importBody(rawBody: unknown): Pick<HttpRequest, 'body' | 'bodyType' | '
|
||||
})),
|
||||
},
|
||||
};
|
||||
} else if (body.mode === 'formdata') {
|
||||
}
|
||||
if (body.mode === 'formdata') {
|
||||
return {
|
||||
headers: [
|
||||
{
|
||||
@@ -459,7 +461,8 @@ function importBody(rawBody: unknown): Pick<HttpRequest, 'body' | 'bodyType' | '
|
||||
),
|
||||
},
|
||||
};
|
||||
} else if (body.mode === 'raw') {
|
||||
}
|
||||
if (body.mode === 'raw') {
|
||||
return {
|
||||
headers: [
|
||||
{
|
||||
@@ -473,7 +476,8 @@ function importBody(rawBody: unknown): Pick<HttpRequest, 'body' | 'bodyType' | '
|
||||
text: body.raw ?? '',
|
||||
},
|
||||
};
|
||||
} else if (body.mode === 'file') {
|
||||
}
|
||||
if (body.mode === 'file') {
|
||||
return {
|
||||
headers: [],
|
||||
bodyType: 'binary',
|
||||
@@ -481,9 +485,8 @@ function importBody(rawBody: unknown): Pick<HttpRequest, 'body' | 'bodyType' | '
|
||||
filePath: body.file?.src,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return { headers: [], bodyType: null, body: {} };
|
||||
}
|
||||
return { headers: [], bodyType: null, body: {} };
|
||||
}
|
||||
|
||||
function parseJSONToRecord<T>(jsonStr: string): Record<string, T> | null {
|
||||
@@ -503,7 +506,7 @@ function toRecord<T>(value: Record<string, T> | unknown): Record<string, T> {
|
||||
|
||||
function toArray<T>(value: unknown): T[] {
|
||||
if (Object.prototype.toString.call(value) === '[object Array]') return value as T[];
|
||||
else return [];
|
||||
return [];
|
||||
}
|
||||
|
||||
/** Recursively render all nested object properties */
|
||||
@@ -511,31 +514,32 @@ function convertTemplateSyntax<T>(obj: T): T {
|
||||
if (typeof obj === 'string') {
|
||||
return obj.replace(
|
||||
/{{\s*(_\.)?([^}]*)\s*}}/g,
|
||||
(_m, _dot, expr) => '${[' + expr.trim().replace(/^vault:/, '') + ']}',
|
||||
(_m, _dot, expr) => `\${[${expr.trim().replace(/^vault:/, '')}]}`,
|
||||
) as T;
|
||||
} else if (Array.isArray(obj) && obj != null) {
|
||||
}
|
||||
if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(convertTemplateSyntax) as T;
|
||||
} else if (typeof obj === 'object' && obj != null) {
|
||||
}
|
||||
if (typeof obj === 'object' && obj != null) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([k, v]) => [k, convertTemplateSyntax(v)]),
|
||||
) as T;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function deleteUndefinedAttrs<T>(obj: T): T {
|
||||
if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(deleteUndefinedAttrs) as T;
|
||||
} else if (typeof obj === 'object' && obj != null) {
|
||||
}
|
||||
if (typeof obj === 'object' && obj != null) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj)
|
||||
.filter(([, v]) => v !== undefined)
|
||||
.map(([k, v]) => [k, deleteUndefinedAttrs(v)]),
|
||||
) as T;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
const idCount: Partial<Record<string, number>> = {};
|
||||
|
||||
1610
plugins/importer-postman/tests/fixtures/auth.input.json
vendored
1610
plugins/importer-postman/tests/fixtures/auth.input.json
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,38 +1,38 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "9e6dfada-256c-49ea-a38f-7d1b05b7ca2d",
|
||||
"name": "New Collection",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json",
|
||||
"_exporter_id": "18798"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Top Folder",
|
||||
"item": [
|
||||
{
|
||||
"name": "Nested Folder",
|
||||
"item": [
|
||||
{
|
||||
"name": "Request 1",
|
||||
"request": {
|
||||
"method": "GET"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Request 2",
|
||||
"request": {
|
||||
"method": "GET"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Request 3",
|
||||
"request": {
|
||||
"method": "GET"
|
||||
}
|
||||
}
|
||||
]
|
||||
"info": {
|
||||
"_postman_id": "9e6dfada-256c-49ea-a38f-7d1b05b7ca2d",
|
||||
"name": "New Collection",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json",
|
||||
"_exporter_id": "18798"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Top Folder",
|
||||
"item": [
|
||||
{
|
||||
"name": "Nested Folder",
|
||||
"item": [
|
||||
{
|
||||
"name": "Request 1",
|
||||
"request": {
|
||||
"method": "GET"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Request 2",
|
||||
"request": {
|
||||
"method": "GET"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Request 3",
|
||||
"request": {
|
||||
"method": "GET"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -47,14 +47,8 @@
|
||||
},
|
||||
"url": {
|
||||
"raw": "example.com/:foo/:bar?q=qqq&",
|
||||
"host": [
|
||||
"example",
|
||||
"com"
|
||||
],
|
||||
"path": [
|
||||
":foo",
|
||||
":bar"
|
||||
],
|
||||
"host": ["example", "com"],
|
||||
"path": [":foo", ":bar"],
|
||||
"query": [
|
||||
{
|
||||
"key": "disabled",
|
||||
@@ -110,9 +104,7 @@
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"packages": {},
|
||||
"exec": [
|
||||
""
|
||||
]
|
||||
"exec": [""]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -120,9 +112,7 @@
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"packages": {},
|
||||
"exec": [
|
||||
""
|
||||
]
|
||||
"exec": [""]
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('importer-postman', () => {
|
||||
continue;
|
||||
}
|
||||
|
||||
test('Imports ' + fixture, () => {
|
||||
test(`Imports ${fixture}`, () => {
|
||||
const contents = fs.readFileSync(path.join(p, fixture), 'utf-8');
|
||||
const expected = fs.readFileSync(path.join(p, fixture.replace('.input', '.output')), 'utf-8');
|
||||
const result = convertPostman(contents);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ export const plugin: PluginDefinition = {
|
||||
};
|
||||
|
||||
export function migrateImport(contents: string) {
|
||||
let parsed;
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
let parsed: any;
|
||||
try {
|
||||
parsed = JSON.parse(contents);
|
||||
} catch {
|
||||
@@ -30,7 +31,7 @@ export function migrateImport(contents: string) {
|
||||
// Migrate v1 to v2 -- changes requests to httpRequests
|
||||
if ('requests' in parsed.resources) {
|
||||
parsed.resources.httpRequests = parsed.resources.requests;
|
||||
delete parsed.resources['requests'];
|
||||
parsed.resources.requests = undefined;
|
||||
}
|
||||
|
||||
// Migrate v2 to v3
|
||||
@@ -38,7 +39,7 @@ export function migrateImport(contents: string) {
|
||||
if ('variables' in workspace) {
|
||||
// Create the base environment
|
||||
const baseEnvironment: Partial<Environment> = {
|
||||
id: `GENERATE_ID::base_env_${workspace['id']}`,
|
||||
id: `GENERATE_ID::base_env_${workspace.id}`,
|
||||
name: 'Global Variables',
|
||||
variables: workspace.variables,
|
||||
workspaceId: workspace.id,
|
||||
@@ -47,7 +48,7 @@ export function migrateImport(contents: string) {
|
||||
parsed.resources.environments.push(baseEnvironment);
|
||||
|
||||
// Delete variables key from the workspace
|
||||
delete workspace.variables;
|
||||
workspace.variables = undefined;
|
||||
|
||||
// Add environmentId to relevant environments
|
||||
for (const environment of parsed.resources.environments) {
|
||||
@@ -62,7 +63,7 @@ export function migrateImport(contents: string) {
|
||||
for (const environment of parsed.resources.environments ?? []) {
|
||||
if ('environmentId' in environment) {
|
||||
environment.base = environment.environmentId == null;
|
||||
delete environment.environmentId;
|
||||
environment.environmentId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,11 +72,11 @@ export function migrateImport(contents: string) {
|
||||
if ('base' in environment && environment.base && environment.parentModel == null) {
|
||||
environment.parentModel = 'workspace';
|
||||
environment.parentId = null;
|
||||
delete environment.base;
|
||||
environment.base = undefined;
|
||||
} else if ('base' in environment && !environment.base && environment.parentModel == null) {
|
||||
environment.parentModel = 'environment';
|
||||
environment.parentId = null;
|
||||
delete environment.base;
|
||||
environment.base = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
19
plugins/template-function-1password/package.json
Normal file
19
plugins/template-function-1password/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@yaak/template-function-1password",
|
||||
"displayName": "1Password Template Functions",
|
||||
"description": "Template function for accessing 1Password secrets",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "run-s build:*",
|
||||
"build:1-build": "yaakcli build",
|
||||
"build:2-cpywasm": "cpx \"../../node_modules/@1password/sdk-core/nodejs/core_bg.wasm\" build/",
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@1password/sdk": "^0.4.0-beta.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cpx": "^1.5.0"
|
||||
}
|
||||
}
|
||||
221
plugins/template-function-1password/src/index.ts
Normal file
221
plugins/template-function-1password/src/index.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
import crypto from 'node:crypto';
|
||||
import type { Client } from '@1password/sdk';
|
||||
import { createClient, DesktopAuth } from '@1password/sdk';
|
||||
import type { JsonPrimitive, PluginDefinition } from '@yaakapp/api';
|
||||
import type { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
|
||||
|
||||
const _clients: Record<string, Client> = {};
|
||||
|
||||
async function op(args: CallTemplateFunctionArgs): Promise<{ client?: Client; error?: unknown }> {
|
||||
let authMethod: string | DesktopAuth | null = null;
|
||||
let hash: string | null = null;
|
||||
switch (args.values.authMethod) {
|
||||
case 'desktop': {
|
||||
const account = args.values.token;
|
||||
if (typeof account !== 'string' || !account) return { error: 'Missing account name' };
|
||||
|
||||
hash = crypto.createHash('sha256').update(`desktop:${account}`).digest('hex');
|
||||
authMethod = new DesktopAuth(account);
|
||||
break;
|
||||
}
|
||||
case 'token': {
|
||||
const token = args.values.token;
|
||||
if (typeof token !== 'string' || !token) return { error: 'Missing service token' };
|
||||
|
||||
hash = crypto.createHash('sha256').update(`token:${token}`).digest('hex');
|
||||
authMethod = token;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hash == null || authMethod == null) return { error: 'Invalid authentication method' };
|
||||
|
||||
try {
|
||||
_clients[hash] ??= await createClient({
|
||||
auth: authMethod,
|
||||
integrationName: 'Yaak 1Password Plugin',
|
||||
integrationVersion: 'v1.0.0',
|
||||
});
|
||||
} catch (e) {
|
||||
return { error: e };
|
||||
}
|
||||
|
||||
return { client: _clients[hash] };
|
||||
}
|
||||
|
||||
async function getValue(
|
||||
args: CallTemplateFunctionArgs,
|
||||
vaultId?: JsonPrimitive,
|
||||
itemId?: JsonPrimitive,
|
||||
fieldId?: JsonPrimitive,
|
||||
): Promise<{ value?: string; error?: unknown }> {
|
||||
const { client, error } = await op(args);
|
||||
if (!client) return { error };
|
||||
|
||||
if (vaultId && typeof vaultId === 'string') {
|
||||
try {
|
||||
await client.vaults.getOverview(vaultId);
|
||||
} catch {
|
||||
return { error: `Vault ${vaultId} not found` };
|
||||
}
|
||||
} else {
|
||||
return { error: 'No vault specified' };
|
||||
}
|
||||
|
||||
if (itemId && typeof itemId === 'string') {
|
||||
try {
|
||||
const item = await client.items.get(vaultId, itemId);
|
||||
if (fieldId && typeof fieldId === 'string') {
|
||||
const field = item.fields.find((f) => f.id === fieldId);
|
||||
if (field) {
|
||||
return { value: field.value };
|
||||
} else {
|
||||
return { error: `Field ${fieldId} not found in item ${itemId} in vault ${vaultId}` };
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return { error: `Item ${itemId} not found in vault ${vaultId}` };
|
||||
}
|
||||
} else {
|
||||
return { error: 'No item specified' };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
templateFunctions: [
|
||||
{
|
||||
name: '1password.item',
|
||||
description: 'Get a secret',
|
||||
previewArgs: ['field'],
|
||||
args: [
|
||||
{
|
||||
type: 'h_stack',
|
||||
inputs: [
|
||||
{
|
||||
name: 'authMethod',
|
||||
type: 'select',
|
||||
label: 'Authentication Method',
|
||||
defaultValue: 'token',
|
||||
options: [
|
||||
{
|
||||
label: 'Service Account',
|
||||
value: 'token',
|
||||
},
|
||||
{
|
||||
label: 'Desktop App',
|
||||
value: 'desktop',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'token',
|
||||
type: 'text',
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Yaak template syntax
|
||||
defaultValue: '${[1PASSWORD_TOKEN]}',
|
||||
dynamic(_ctx, args) {
|
||||
switch (args.values.authMethod) {
|
||||
case 'desktop':
|
||||
return {
|
||||
label: 'Account Name',
|
||||
description:
|
||||
'Account name can be taken from the sidebar of the 1Password App. Make sure you\'re on the BETA version of the 1Password app and have "Integrate with other apps" enabled in Settings > Developer.',
|
||||
};
|
||||
case 'token':
|
||||
return {
|
||||
label: 'Token',
|
||||
description:
|
||||
'Token can be generated from the 1Password website by visiting Developer > Service Accounts',
|
||||
password: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { hidden: true };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'vault',
|
||||
label: 'Vault',
|
||||
type: 'select',
|
||||
options: [],
|
||||
async dynamic(_ctx, args) {
|
||||
const { client } = await op(args);
|
||||
if (client == null) return { hidden: true };
|
||||
// Fetches a secret.
|
||||
const vaults = await client.vaults.list({ decryptDetails: true });
|
||||
return {
|
||||
options: vaults.map((vault) => ({
|
||||
label: `${vault.title} (${vault.activeItemCount} Items)`,
|
||||
value: vault.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'item',
|
||||
label: 'Item',
|
||||
type: 'select',
|
||||
options: [],
|
||||
async dynamic(_ctx, args) {
|
||||
const { client } = await op(args);
|
||||
if (client == null) return { hidden: true };
|
||||
const vaultId = args.values.vault;
|
||||
if (typeof vaultId !== 'string') return { hidden: true };
|
||||
|
||||
try {
|
||||
const items = await client.items.list(vaultId);
|
||||
return {
|
||||
options: items.map((item) => ({
|
||||
label: `${item.title} ${item.category}`,
|
||||
value: item.id,
|
||||
})),
|
||||
};
|
||||
} catch {
|
||||
// Hide as we can't list the items for this vault
|
||||
return { hidden: true };
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'field',
|
||||
label: 'Field',
|
||||
type: 'select',
|
||||
options: [],
|
||||
async dynamic(_ctx, args) {
|
||||
const { client } = await op(args);
|
||||
if (client == null) return { hidden: true };
|
||||
const vaultId = args.values.vault;
|
||||
const itemId = args.values.item;
|
||||
if (typeof vaultId !== 'string' || typeof itemId !== 'string') {
|
||||
return { hidden: true };
|
||||
}
|
||||
|
||||
try {
|
||||
const item = await client.items.get(vaultId, itemId);
|
||||
return {
|
||||
options: item.fields.map((field) => ({ label: field.title, value: field.id })),
|
||||
};
|
||||
} catch {
|
||||
// Hide as we can't find the item within this vault
|
||||
return { hidden: true };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
async onRender(_ctx, args) {
|
||||
const vaultId = args.values.vault;
|
||||
const itemId = args.values.item;
|
||||
const fieldId = args.values.field;
|
||||
const { value, error } = await getValue(args, vaultId, itemId, fieldId);
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return value ?? '';
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
3
plugins/template-function-1password/tsconfig.json
Normal file
3
plugins/template-function-1password/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json"
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,18 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'cookie.value',
|
||||
description: 'Read the value of a cookie in the jar, by name',
|
||||
previewArgs: ['name'],
|
||||
args: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'cookie_name',
|
||||
name: 'name',
|
||||
label: 'Cookie Name',
|
||||
},
|
||||
],
|
||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
return ctx.cookies.getValue({ name: String(args.values.cookie_name) });
|
||||
// The legacy name was cookie_name, but we changed it
|
||||
const name = args.values.cookie_name ?? args.values.name;
|
||||
return ctx.cookies.getValue({ name: String(name) });
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||
import fs from 'node:fs';
|
||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||
|
||||
const UTF8 = 'utf8';
|
||||
const options = [
|
||||
@@ -16,6 +16,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'fs.readFile',
|
||||
description: 'Read the contents of a file as utf-8',
|
||||
previewArgs: ['encoding'],
|
||||
args: [
|
||||
{ title: 'Select File', type: 'file', name: 'path', label: 'File' },
|
||||
{
|
||||
@@ -26,14 +27,21 @@ export const plugin: PluginDefinition = {
|
||||
description: "Specifies how the file's bytes are decoded into text when read",
|
||||
options,
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'trim',
|
||||
label: 'Trim Whitespace',
|
||||
description: 'Remove leading and trailing whitespace from the file contents',
|
||||
},
|
||||
],
|
||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
if (!args.values.path || !args.values.encoding) return null;
|
||||
|
||||
try {
|
||||
return fs.promises.readFile(String(args.values.path ?? ''), {
|
||||
const v = await fs.promises.readFile(String(args.values.path ?? ''), {
|
||||
encoding: String(args.values.encoding ?? 'utf-8') as BufferEncoding,
|
||||
});
|
||||
return args.values.trim ? v.trim() : v;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||
import { createHash, createHmac } from 'node:crypto';
|
||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||
|
||||
const algorithms = ['md5', 'sha1', 'sha256', 'sha512'] as const;
|
||||
const encodings = ['base64', 'hex'] as const;
|
||||
|
||||
type TemplateFunctionPlugin = NonNullable<PluginDefinition['templateFunctions']>[number];
|
||||
|
||||
const hashFunctions: TemplateFunctionPlugin[] = algorithms.map(algorithm => ({
|
||||
const hashFunctions: TemplateFunctionPlugin[] = algorithms.map((algorithm) => ({
|
||||
name: `hash.${algorithm}`,
|
||||
description: 'Hash a value to its hexadecimal representation',
|
||||
args: [
|
||||
@@ -22,7 +22,7 @@ const hashFunctions: TemplateFunctionPlugin[] = algorithms.map(algorithm => ({
|
||||
name: 'encoding',
|
||||
label: 'Encoding',
|
||||
defaultValue: 'base64',
|
||||
options: encodings.map(encoding => ({
|
||||
options: encodings.map((encoding) => ({
|
||||
label: capitalize(encoding),
|
||||
value: encoding,
|
||||
})),
|
||||
@@ -30,15 +30,13 @@ const hashFunctions: TemplateFunctionPlugin[] = algorithms.map(algorithm => ({
|
||||
],
|
||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
const input = String(args.values.input);
|
||||
const encoding = String(args.values.encoding) as typeof encodings[number];
|
||||
const encoding = String(args.values.encoding) as (typeof encodings)[number];
|
||||
|
||||
return createHash(algorithm)
|
||||
.update(input, 'utf-8')
|
||||
.digest(encoding);
|
||||
return createHash(algorithm).update(input, 'utf-8').digest(encoding);
|
||||
},
|
||||
}));
|
||||
|
||||
const hmacFunctions: TemplateFunctionPlugin[] = algorithms.map(algorithm => ({
|
||||
const hmacFunctions: TemplateFunctionPlugin[] = algorithms.map((algorithm) => ({
|
||||
name: `hmac.${algorithm}`,
|
||||
description: 'Compute the HMAC of a value',
|
||||
args: [
|
||||
@@ -60,7 +58,7 @@ const hmacFunctions: TemplateFunctionPlugin[] = algorithms.map(algorithm => ({
|
||||
name: 'encoding',
|
||||
label: 'Encoding',
|
||||
defaultValue: 'base64',
|
||||
options: encodings.map(encoding => ({
|
||||
options: encodings.map((encoding) => ({
|
||||
value: encoding,
|
||||
label: capitalize(encoding),
|
||||
})),
|
||||
@@ -69,11 +67,9 @@ const hmacFunctions: TemplateFunctionPlugin[] = algorithms.map(algorithm => ({
|
||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
const input = String(args.values.input);
|
||||
const key = String(args.values.key);
|
||||
const encoding = String(args.values.encoding) as typeof encodings[number];
|
||||
const encoding = String(args.values.encoding) as (typeof encodings)[number];
|
||||
|
||||
return createHmac(algorithm, key, {})
|
||||
.update(input)
|
||||
.digest(encoding);
|
||||
return createHmac(algorithm, key, {}).update(input).digest(encoding);
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonpath-plus": "^10.3.0"
|
||||
|
||||
@@ -11,6 +11,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'json.jsonpath',
|
||||
description: 'Filter JSON-formatted text using JSONPath syntax',
|
||||
previewArgs: ['query'],
|
||||
args: [
|
||||
{
|
||||
type: 'editor',
|
||||
@@ -118,7 +119,7 @@ export function filterJSONPath(
|
||||
path: string,
|
||||
result: JSONPathResult,
|
||||
join: string | null,
|
||||
formatted: boolean = false,
|
||||
formatted = false,
|
||||
): string {
|
||||
const parsed = JSON.parse(body);
|
||||
let items = JSONPath({ path, json: parsed });
|
||||
@@ -138,13 +139,12 @@ export function filterJSONPath(
|
||||
return objToStr(items, formatted);
|
||||
}
|
||||
|
||||
function objToStr(o: unknown, formatted: boolean = false): string {
|
||||
function objToStr(o: unknown, formatted = false): string {
|
||||
if (
|
||||
Object.prototype.toString.call(o) === '[object Array]' ||
|
||||
Object.prototype.toString.call(o) === '[object Object]'
|
||||
) {
|
||||
return formatted ? JSON.stringify(o, null, 2) : JSON.stringify(o);
|
||||
} else {
|
||||
return String(o);
|
||||
}
|
||||
return String(o);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"slugify": "^1.6.6"
|
||||
|
||||
@@ -16,8 +16,22 @@ export const plugin: PluginDefinition = {
|
||||
name: 'prompt.text',
|
||||
description: 'Prompt the user for input when sending a request',
|
||||
previewType: 'click',
|
||||
previewArgs: ['label'],
|
||||
args: [
|
||||
{ type: 'text', name: 'label', label: 'Label' },
|
||||
{
|
||||
type: 'text',
|
||||
name: 'label',
|
||||
label: 'Label',
|
||||
optional: true,
|
||||
dynamic(_ctx, args) {
|
||||
if (
|
||||
args.values.store === STORE_EXPIRE ||
|
||||
(args.values.store === STORE_FOREVER && !args.values.key)
|
||||
) {
|
||||
return { optional: false };
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'store',
|
||||
@@ -39,6 +53,7 @@ export const plugin: PluginDefinition = {
|
||||
type: 'text',
|
||||
name: 'namespace',
|
||||
label: 'Namespace',
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Yaak template syntax
|
||||
defaultValue: '${[ctx.workspace()]}',
|
||||
optional: true,
|
||||
},
|
||||
@@ -67,21 +82,24 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
type: 'banner',
|
||||
color: 'info',
|
||||
inputs: [],
|
||||
dynamic(_ctx, args) {
|
||||
return { hidden: args.values.store === STORE_NONE };
|
||||
let key: string;
|
||||
try {
|
||||
key = buildKey(args);
|
||||
} catch (err) {
|
||||
return { color: 'danger', inputs: [{ type: 'markdown', content: String(err) }] };
|
||||
}
|
||||
return {
|
||||
hidden: args.values.store === STORE_NONE,
|
||||
inputs: [
|
||||
{
|
||||
type: 'markdown',
|
||||
content: [`Value will be saved under: \`${key}\``].join('\n\n'),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
type: 'markdown',
|
||||
content: '',
|
||||
async dynamic(_ctx, args) {
|
||||
const key = buildKey(args);
|
||||
return {
|
||||
content: ['Value will be saved under: `' + key + '`'].join('\n\n'),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'accordion',
|
||||
@@ -104,7 +122,7 @@ export const plugin: PluginDefinition = {
|
||||
if (args.purpose !== 'send') return null;
|
||||
|
||||
if (args.values.store !== STORE_NONE && !args.values.namespace) {
|
||||
throw new Error('Namespace is required when storing values')
|
||||
throw new Error('Namespace is required when storing values');
|
||||
}
|
||||
|
||||
const existing = await maybeGetValue(ctx, args);
|
||||
@@ -137,6 +155,9 @@ export const plugin: PluginDefinition = {
|
||||
};
|
||||
|
||||
function buildKey(args: CallTemplateFunctionArgs) {
|
||||
if (!args.values.key && !args.values.label) {
|
||||
throw new Error('A label or key is required when storing values');
|
||||
}
|
||||
return [args.values.namespace, args.values.key || args.values.label]
|
||||
.filter((v) => !!v)
|
||||
.map((v) => slugify(String(v), { lower: true, trim: true }))
|
||||
@@ -155,7 +176,7 @@ async function maybeGetValue(ctx: Context, args: CallTemplateFunctionArgs) {
|
||||
return existing.value;
|
||||
}
|
||||
|
||||
const ttlSeconds = parseInt(String(args.values.ttl)) || 0;
|
||||
const ttlSeconds = Number.parseInt(String(args.values.ttl), 10) || 0;
|
||||
const ageSeconds = (Date.now() - existing.createdAt) / 1000;
|
||||
if (ageSeconds > ttlSeconds) {
|
||||
ctx.store.delete(buildKey(args)).catch(console.error);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"dev": "yaakcli dev"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'random.range',
|
||||
description: 'Generate a random number between two values',
|
||||
previewArgs: ['min', 'max'],
|
||||
args: [
|
||||
{
|
||||
type: 'text',
|
||||
@@ -26,15 +27,15 @@ export const plugin: PluginDefinition = {
|
||||
},
|
||||
],
|
||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
const min = args.values.min ? parseInt(String(args.values.min ?? '0')) : 0;
|
||||
const max = args.values.max ? parseInt(String(args.values.max ?? '1')) : 1;
|
||||
const min = args.values.min ? Number.parseInt(String(args.values.min ?? '0'), 10) : 0;
|
||||
const max = args.values.max ? Number.parseInt(String(args.values.max ?? '1'), 10) : 1;
|
||||
const decimals = args.values.decimals
|
||||
? parseInt(String(args.values.decimals ?? '0'))
|
||||
? Number.parseInt(String(args.values.decimals ?? '0'), 10)
|
||||
: null;
|
||||
|
||||
let value = Math.random() * (max - min) + min;
|
||||
if (decimals !== null) {
|
||||
value = Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
||||
value = Math.round(value * 10 ** decimals) / 10 ** decimals;
|
||||
}
|
||||
return String(value);
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TemplateFunctionArg } from '@yaakapp-internal/plugins';
|
||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||
import type { TemplateFunctionArg } from '@yaakapp-internal/plugins';
|
||||
|
||||
const inputArg: TemplateFunctionArg = {
|
||||
type: 'text',
|
||||
@@ -24,6 +24,7 @@ export const plugin: PluginDefinition = {
|
||||
name: 'regex.match',
|
||||
description: 'Extract text using a regular expression',
|
||||
args: [inputArg, regexArg],
|
||||
previewArgs: [regexArg.name],
|
||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
const input = String(args.values.input ?? '');
|
||||
const regex = new RegExp(String(args.values.regex ?? ''));
|
||||
@@ -37,6 +38,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'regex.replace',
|
||||
description: 'Replace text using a regular expression',
|
||||
previewArgs: [regexArg.name],
|
||||
args: [
|
||||
inputArg,
|
||||
regexArg,
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { plugin } from '../src';
|
||||
|
||||
describe('regex.match', () => {
|
||||
const matchFunction = plugin.templateFunctions!.find(f => f.name === 'regex.match');
|
||||
const matchFunction = plugin.templateFunctions?.find((f) => f.name === 'regex.match');
|
||||
|
||||
it('should exist', () => {
|
||||
expect(matchFunction).toBeDefined();
|
||||
});
|
||||
|
||||
it('should extract first capture group', async () => {
|
||||
const result = await matchFunction!.onRender({} as Context, {
|
||||
const result = await matchFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'Hello (\\w+)',
|
||||
input: 'Hello World',
|
||||
@@ -21,7 +21,7 @@ describe('regex.match', () => {
|
||||
});
|
||||
|
||||
it('should extract named capture group', async () => {
|
||||
const result = await matchFunction!.onRender({} as Context, {
|
||||
const result = await matchFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'Hello (?<name>\\w+)',
|
||||
input: 'Hello World',
|
||||
@@ -32,10 +32,10 @@ describe('regex.match', () => {
|
||||
});
|
||||
|
||||
it('should return full match when no capture groups', async () => {
|
||||
const result = await matchFunction!.onRender({} as Context, {
|
||||
const result = await matchFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'Hello \\w+',
|
||||
input: 'Hello World'
|
||||
input: 'Hello World',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -43,10 +43,10 @@ describe('regex.match', () => {
|
||||
});
|
||||
|
||||
it('should return empty string when no match', async () => {
|
||||
const result = await matchFunction!.onRender({} as Context, {
|
||||
const result = await matchFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'Goodbye',
|
||||
input: 'Hello World'
|
||||
input: 'Hello World',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -54,10 +54,10 @@ describe('regex.match', () => {
|
||||
});
|
||||
|
||||
it('should return empty string when regex is empty', async () => {
|
||||
const result = await matchFunction!.onRender({} as Context, {
|
||||
const result = await matchFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: '',
|
||||
input: 'Hello World'
|
||||
input: 'Hello World',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -65,10 +65,10 @@ describe('regex.match', () => {
|
||||
});
|
||||
|
||||
it('should return empty string when input is empty', async () => {
|
||||
const result = await matchFunction!.onRender({} as Context, {
|
||||
const result = await matchFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'Hello',
|
||||
input: ''
|
||||
input: '',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -77,18 +77,18 @@ describe('regex.match', () => {
|
||||
});
|
||||
|
||||
describe('regex.replace', () => {
|
||||
const replaceFunction = plugin.templateFunctions!.find(f => f.name === 'regex.replace');
|
||||
const replaceFunction = plugin.templateFunctions?.find((f) => f.name === 'regex.replace');
|
||||
|
||||
it('should exist', () => {
|
||||
expect(replaceFunction).toBeDefined();
|
||||
});
|
||||
|
||||
it('should replace one occurrence by default', async () => {
|
||||
const result = await replaceFunction!.onRender({} as Context, {
|
||||
const result = await replaceFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'o',
|
||||
input: 'Hello World',
|
||||
replacement: 'a'
|
||||
replacement: 'a',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -96,11 +96,11 @@ describe('regex.replace', () => {
|
||||
});
|
||||
|
||||
it('should replace with capture groups', async () => {
|
||||
const result = await replaceFunction!.onRender({} as Context, {
|
||||
const result = await replaceFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: '(\\w+) (\\w+)',
|
||||
input: 'Hello World',
|
||||
replacement: '$2 $1'
|
||||
replacement: '$2 $1',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -108,11 +108,11 @@ describe('regex.replace', () => {
|
||||
});
|
||||
|
||||
it('should replace with full match reference', async () => {
|
||||
const result = await replaceFunction!.onRender({} as Context, {
|
||||
const result = await replaceFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'World',
|
||||
input: 'Hello World',
|
||||
replacement: '[$&]'
|
||||
replacement: '[$&]',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -120,12 +120,12 @@ describe('regex.replace', () => {
|
||||
});
|
||||
|
||||
it('should respect flags parameter', async () => {
|
||||
const result = await replaceFunction!.onRender({} as Context, {
|
||||
const result = await replaceFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'hello',
|
||||
input: 'Hello World',
|
||||
replacement: 'Hi',
|
||||
flags: 'i'
|
||||
flags: 'i',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -133,11 +133,11 @@ describe('regex.replace', () => {
|
||||
});
|
||||
|
||||
it('should handle empty replacement', async () => {
|
||||
const result = await replaceFunction!.onRender({} as Context, {
|
||||
const result = await replaceFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'World',
|
||||
input: 'Hello World',
|
||||
replacement: ''
|
||||
replacement: '',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -145,11 +145,11 @@ describe('regex.replace', () => {
|
||||
});
|
||||
|
||||
it('should return original input when no match', async () => {
|
||||
const result = await replaceFunction!.onRender({} as Context, {
|
||||
const result = await replaceFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'Goodbye',
|
||||
input: 'Hello World',
|
||||
replacement: 'Hi'
|
||||
replacement: 'Hi',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -157,11 +157,11 @@ describe('regex.replace', () => {
|
||||
});
|
||||
|
||||
it('should return empty string when regex is empty', async () => {
|
||||
const result = await replaceFunction!.onRender({} as Context, {
|
||||
const result = await replaceFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: '',
|
||||
input: 'Hello World',
|
||||
replacement: 'Hi'
|
||||
replacement: 'Hi',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -169,11 +169,11 @@ describe('regex.replace', () => {
|
||||
});
|
||||
|
||||
it('should return empty string when input is empty', async () => {
|
||||
const result = await replaceFunction!.onRender({} as Context, {
|
||||
const result = await replaceFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: 'Hello',
|
||||
input: '',
|
||||
replacement: 'Hi'
|
||||
replacement: 'Hi',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
@@ -181,14 +181,16 @@ describe('regex.replace', () => {
|
||||
});
|
||||
|
||||
it('should throw on invalid regex', async () => {
|
||||
const fn = replaceFunction!.onRender({} as Context, {
|
||||
const fn = replaceFunction?.onRender({} as Context, {
|
||||
values: {
|
||||
regex: '[',
|
||||
input: 'Hello World',
|
||||
replacement: 'Hi'
|
||||
replacement: 'Hi',
|
||||
},
|
||||
purpose: 'send',
|
||||
});
|
||||
await expect(fn).rejects.toThrow('Invalid regular expression: /[/: Unterminated character class');
|
||||
await expect(fn).rejects.toThrow(
|
||||
'Invalid regular expression: /[/: Unterminated character class',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user