* chore: add AGENTS.md * Apply batched suggestions from code review [skip ci] Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com> * simplify --------- Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
11 KiB
AGENTS.md
Instructions for coding agents working in this repository. The contributor guide is .github/CONTRIBUTING.md.
Overview
This repo holds the official Tauri plugins. Each directory in plugins/ is one plugin, published as:
- the Rust crate
tauri-plugin-<name>on crates.io, and - the npm package
@tauri-apps/plugin-<name>, its JavaScript bindings for the webview, at the same version as the crate.
Some plugins are Rust-only and have no npm package.
Each Tauri major version has a branch here (v1, v2, …) with the plugins compatible with it. PRs go to the default branch. Don't port changes between them unless asked.
Layout
plugins/<name>/
build.rs lists the plugin's commands and generates its permissions
src/lib.rs init()/Builder, plus a `<Name>Ext` trait on AppHandle & co.
src/commands.rs #[tauri::command] handlers
src/desktop.rs, mobile.rs platform implementations; mobile.rs calls into Kotlin/Swift
src/error.rs thiserror Error, serialized to JS as its message string
src/models.rs serde types shared with JS and native code
permissions/*.toml hand-written permission sets, including default.toml
permissions/autogenerated/ generated
permissions/schemas/ generated
guest-js/index.ts JS API: thin wrappers around invoke('plugin:<name>|<command>')
api-iife.js generated: the window.__TAURI__.<name> global (withGlobalTauri)
android/, ios/ native plugin code (Kotlin library, Swift package)
examples/ small per-plugin example apps
examples/api/ Svelte app that registers every plugin; the manual test bed
packages/api-e2e/ WebdriverIO e2e suite driven against examples/api
shared/rollup.config.js the rollup config every plugin builds with (createConfig)
shared/template/ skeleton for a new plugin ({{ placeholders }}, not compiled)
.changes/ covector change files
.scripts/ci/ scripts CI runs; they work locally too
Build, lint, test
Run everything from the repo root.
pnpm install
pnpm build # every plugin: dist-js/, api-iife.js, src/init-iife.js
pnpm --filter @tauri-apps/plugin-<name> build # one plugin
Run pnpm build before pnpm lint, pnpm ts:check, the example app or the e2e suite, because they read the plugins from dist-js/.
Rust checks run per crate. CI runs them for every plugin a change affects, on Windows, Linux and macOS, and builds for iOS and Android:
cargo clippy -p tauri-plugin-<name> --all-targets -- -D warnings
cargo clippy -p tauri-plugin-<name> --all-targets --all-features -- -D warnings
cargo test -p tauri-plugin-<name> --all-targets --all-features
cargo test -p tauri-plugin-<name> --doc
Some plugins have mutually exclusive features (check [features] in their Cargo.toml) and don't build with --all-features. Drop that flag for them.
Formatting and JS checks. CI fails if any of them would change a file or report an error:
cargo fmt --all
taplo fmt # TOML
pnpm format # prettier: TS, JS, JSON, Markdown, YAML
pnpm lint # eslint with type-aware rules
pnpm ts:check
To try a change by hand, run pnpm example:api:dev. It starts examples/api against the local plugins.
The e2e suite runs with pnpm test:api-e2e (test:api-e2e:android and test:api-e2e:ios for mobile). Its prerequisites, environment variables and how to add a spec are in packages/api-e2e/README.md.
Generated files
Don't edit these by hand. Regenerate them and commit the result together with the change that caused it.
| Files | Regenerated by |
|---|---|
plugins/*/api-iife.js, plugins/*/src/init-iife.js |
pnpm build. CI fails if the build leaves a diff. |
plugins/*/permissions/autogenerated/**, plugins/*/permissions/schemas/schema.json |
the plugin's build.rs, on any cargo check or build of it |
CHANGELOG.md files, package versions, the version of path dependencies |
covector, at release time. Don't bump versions in a PR. |
Also leave dist-js/ alone, along with the git-ignored .tauri/ directories under plugins/*/ and plugins/*/android/. The build copies Tauri's mobile API into those.
Adding or changing a command
These places must agree:
src/commands.rs: write the#[tauri::command], returningcrate::Result<T>.src/lib.rs: register it intauri::generate_handler![…]on every platform where the JS API exposes it. If it can't work on a platform, register it anyway and return an error there. An unregistered command reaches JS as an opaque "command not found".build.rs: add it toCOMMANDS, which generates itsallow-…anddeny-…permissions. Without them, apps can't grant it. When you remove a command, also delete its file frompermissions/autogenerated/commands/, because the build doesn't remove stale ones.permissions/default.toml: add it only if every app should get it without opting in. Defaults are deliberately narrow.guest-js/index.ts: callinvoke('plugin:<name>|<command_name>', { … }). JS passes the command's snake_case parameters as camelCase keys (file_pathbecomesfilePath), and serde structs use#[serde(rename_all = "camelCase")]. Give exported functions TSDoc with an@example, and set@sinceto the version the change will ship in.- Mobile:
mobile.rsforwards withrun_mobile_plugin("<methodName>", args)to the Kotlin@Commandor Swift@objcmethod of that name. - Example app: if it should use the command, grant it in
examples/api/src-tauri/capabilities/and cover it inpackages/api-e2e. - Run
cargo check -p tauri-plugin-<name>andpnpm build, then commit the regenerated files.
Change files
A change to a published crate or npm package needs a covector change file in .changes/. Changes that only touch CI, tests, examples or docs don't. Here is a real one, .changes/fix-fs-read-lines-error.md:
---
fs: patch
fs-js: patch
---
Fixed `readTextFileLines` yielding empty lines forever when reading fails (e.g. on a directory). The read error is now reported: the iterator rejects and the file is closed.
- The keys are package names from
.changes/config.json.<plugin>is the crate and<plugin>-jsis the npm package. - List both with the same bump, even when only the Rust side or only the JS side changed. Their versions are kept identical. Rust-only plugins have no
-jsentry. - The bump is
patch,minorormajor. Plugins that depend on the changed one (dialogonfs, for example) are bumped automatically, so don't list them. - The body becomes the changelog entry. Write it for the plugin's users: what changed, and what they must update.
- Check the file with
node .scripts/ci/check-change-files.js .changes/<file>.md.
Rules and gotchas
-
License header: new
.rs,.ts,.js,.kt,.swiftand.ymlfiles must start with these lines (with#comments in YAML). Keep the year range exactly as written:// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MITCheck new files with
node .scripts/ci/check-license-header.js <files>. Without arguments, it also reports the git-ignored.tauri/copies, which you can ignore. -
MSRV: the tests run on exactly on MSRV configured in Cargo.toml (clippy runs on stable). Don't use newer std APIs, and check with
cargo +<version> check -p tauri-plugin-<name>where is the current MSRV for the plugin. -
tauri features: the workspace depends on
tauriwithdefault-features = false. A plugin that needs a tauri feature, such aswryon iOS, enables it in its ownCargo.toml. Build plugins with-p: a workspace-wide build unifies features withexamples/api, which hides a missing one. -
Platform cfgs: use the
desktopandmobilecfgs thattauri-plugin's build script sets, andtarget_osfor single platforms. Desktop-only dependencies go in the longcfg(any(target_os = "macos", windows, target_os = "linux", …))target table that the existing manifests use. -
docs.rs: a plugin with mobile code must detect docs.rs in its
build.rswithstd::env::var_os("DOCS_RS"), asplugins/dialog/build.rsdoes.cfg!(docsrs)is never set for build scripts, andshared/template/build.rsstill has that old form. -
Dependencies: shared ones come from
[workspace.dependencies](serde = { workspace = true }). A plugin that depends on another uses a path plus a version (tauri-plugin-fs = { path = "../fs", version = "2.6.0" }). -
Updates: Renovate handles routine dependency updates, so don't run a blanket
cargo updateorpnpm update. Unrelated lockfile changes make CI build and test every plugin. -
New npm versions: pnpm won't install versions published less than 3 days ago (
minimumReleaseAge). Ask before adding an exception topnpm-workspace.yaml. -
Platform support: when a plugin gains or loses a platform, update
[package.metadata.platforms.support]in itsCargo.toml, the table in its README and the table in the root README. -
Mirrors: plugins listed in
plugins/mirrors.txtare pushed to read-onlytauri-apps/tauri-plugin-<name>repositories. Make changes here, not in the mirrors. -
Security: report vulnerabilities privately, as SECURITY.md describes. Never put them in a public issue or PR.
Commits and pull requests
- Use conventional-commit titles scoped to the plugin:
fix(fs): …,feat(notification): …,chore(deps): …,ci: …,docs(log): …. When the change fixes an issue, end the title with(fix #1234). - Commits must be signed, so don't bypass signing. PRs are squash-merged, so intermediate commits are fine.
- A new feature needs a convincing reason, ideally an issue the maintainers accepted. Link it in the PR.
- AI tool policy: the person submitting is responsible for everything an agent wrote, and replies to review comments must not be AI-written (translations excepted). PRs that ignore this are closed as
ai-slop. Before handing work over, run the checks above and say plainly what you tested and on which platforms. Don't draft or post replies to review comments.