npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@iyulab/tauri-kit-dev

v0.9.0

Published

Development and verification tools for Tauri desktop apps: drive the WebView2 window over CDP, check installers, and keep public text clean. Nothing here ships inside an app.

Readme

tauri-kit-dev

Development and verification tools for desktop apps made with Tauri — the scripts most such apps end up writing for themselves to drive their window in end-to-end runs, check their installers and keep their public repositories clean. Nothing here ships inside an app; for runtime building blocks, see tauri-kit.

npm install --save-dev @iyulab/tauri-kit-dev

Requires Node 22.4 or later. What it gives an app, and what it deliberately leaves to the app, is in docs/SCOPE.md.

Driving the window over CDP

WebView2 opens a Chrome DevTools Protocol port when its browser starts with --remote-debugging-port=<port>. How a Tauri app's window gets that argument is under Opening the debugging port below — the environment variable alone is not enough.

import { writeFile } from 'node:fs/promises'
import { Cdp, findPage } from '@iyulab/tauri-kit-dev/cdp'

const page = await findPage(9222)
const cdp = await Cdp.connect(page.webSocketDebuggerUrl)
await cdp.waitFor(`document.readyState === 'complete'`, 'the page')
await cdp.evaluate(`document.querySelector('input').focus()`)
await cdp.insertText('hello')
await writeFile('window.png', await cdp.screenshot())
cdp.close()

portAnswers(port) tells whether something is already listening — a window left over from an earlier run would otherwise answer in place of the one just started.

Opening the debugging port

App.launch passes the port in WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS (debugEnv). Current WebView2 runtimes do not apply that variable to an app that passes browser arguments of its own, and a Tauri app always does: its web view library passes defaults. The variable reaches the app's process but not its browser, the port never opens, and the launch fails — saying so, read from the browser's command line. The window has to carry the port in its own browser arguments, in one of two ways:

  • The debug build forwards the variable — the port is still chosen per launch. Declare the window with "create": false, and open it in setup with the variable's arguments added to its own:

    for declared in &app.config().app.windows {
        let mut window = declared.clone();
        // `None` in a release build: an installed app never opens a debugging port because of a variable.
        if let Some(arguments) = tauri_kit_webview::forwarded_browser_arguments(DEFAULT_ARGUMENTS) {
            window.additional_browser_args = Some(arguments);
        }
        tauri::WebviewWindowBuilder::from_config(app.handle(), &window)?.build()?;
    }

    (forwarded_browser_arguments is in tauri-kit's tauri-kit-webview from 0.15; before that, read the variable in a debug build and append it the same way.)

    Giving a window browser arguments replaces the ones the web view library passes by default, so DEFAULT_ARGUMENTS repeats them: --disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection, and --autoplay-policy=no-user-gesture-required while autoplay is on.

  • A test configuration carries a fixed port — tauri dev --config <file> (or a build with that configuration) whose windows have "additionalBrowserArgs" with --remote-debugging-port=<port> and the same defaults. Launch with that port and debugPortFromEnv: false.

Running scenarios against the app

import { App, runScenarios } from '@iyulab/tauri-kit-dev/app'

const options = {
  exe: 'target/debug/my-app.exe',
  port: 9224,
  ready: `!!document.querySelector('my-app')`, // when the window counts as ready is the app's to say
}

process.exitCode = await runScenarios(
  {
    'opens the settings': async (app) => {
      await app.click('button', 'Settings')
      await app.cdp.waitFor(`__e2e.one('h1', 'Settings')`, 'the settings page')
    },
    'saves a name': async (app) => {
      await app.fill('input[aria-label="Name"]', 'Ada')
      await app.cdp.press('Enter')
    },
  },
  { start: async () => ({ app: await App.launch(options) }) },
)

Scenarios run in order against one window, each building on what the last left. --through <part of a name> stops after the first scenario whose name contains it; --only <part of a name> runs just the scenarios whose names contain it, for ones that stand on their own such as measurements; --repeat <n> runs that many times, each from a fresh start — for a scenario that fails only sometimes. E2E_SCREENSHOTS=<dir> saves a picture after each scenario. A failure's picture is kept either way — in that folder, or else in a new one under the temp folder (the failures option) — and the run says where.

A scenario that has not finished after five minutes (the scenarioTimeoutMs option) fails with did not finish within <n> s, and the run goes on to quit the window: a step with no deadline of its own — a protocol command a window that stopped answering never answers — cannot hold the run until a CI job's time limit ends it with nothing said. This bounds only what yields to the event loop: a scenario that runs a program with a blocking call (execFileSync) holds the whole process, so run programs asynchronously with a timeout of their own.

The window under test reads its bundled resources from the copies a build leaves next to the executable (target/<profile>/…), and the build copies files in but never removes one: a file deleted from the source, or left there by a build of another branch, is still read by the scenarios. Removing the copy before the build makes it the source's again — in src-tauri/build.rs, for a resource directory bundled as assets:

fn main() {
    // Watch the source directory so that a file removed from it runs this again.
    println!("cargo:rerun-if-changed=../assets");
    // OUT_DIR is target/<profile>/build/<crate>-<hash>/out; three levels up is the profile directory.
    if let Some(profile) = std::env::var_os("OUT_DIR").as_deref().map(std::path::Path::new).and_then(|out| out.ancestors().nth(3)) {
        let _ = std::fs::remove_dir_all(profile.join("assets"));
    }
    tauri_build::build()
}

Checking the installer

import { join } from 'node:path'
import { findInstaller, startsAndStays, withInstalled } from '@iyulab/tauri-kit-dev/installer'

const installer = findInstaller('target/release/bundle/nsis', { version: '1.2.0' })
await withInstalled(installer, async (target) => {
  // installed silently for the current user into a temporary folder
  if (!(await startsAndStays(join(target, 'my-app.exe')))) throw new Error('the installed app did not stay up')
}, { exe: 'my-app.exe' }) // uninstalled afterwards, and checked to be gone

Installing over the latest published version (pickUpgradeFrom, downloadInstaller) and checking that WebView2 profile copies are dropped (profileSnapshots, seedProfileSnapshot) use the same pieces.

An installed copy keeps its data where the person's own copy does (%LOCALAPPDATA%\<identifier>), and startsAndStays ends it by force. Wrap a check in withAppDataSetAside so whatever the app writes there — its identity, session or diagnostic records, the web view profile — does not stay behind for the next real launch:

import { withAppDataSetAside } from '@iyulab/tauri-kit-dev/installer'

await withAppDataSetAside(join(process.env.LOCALAPPDATA, 'com.example.my-app'), async () => {
  // the folder is moved aside first, so the check starts with none; put back afterwards
})

On a computer without internet, in Windows Sandbox:

npx tauri-kit-dev sandbox <installer> --exe my-app.exe [--extra checks.ps1] [--without-webview2]

The script inside records each step and writes a result the command reads back; --extra adds the app's own steps (Step '<name>' { ... }, with $target the install folder). Windows Sandbox is an optional Windows feature (Containers-DisposableClientVM).

Checking the build machine

npx tauri-kit-dev machine [--dotnet]

On Windows, the Rust toolchain must be MSVC; with --dotnet, a per-user .NET install needs DOTNET_ROOT for a bundled .NET helper process.

Third-party notices

npx tauri-kit-dev notices --config notices.config.js [--strict] [--check]

Writes the notices an installer ships next to the app: every package the app's package managers say it ships — the web UI's npm packages, the shell's crates, a bundled .NET helper's NuGet packages — with the license texts each carries, or the text pinned for one that publishes without it. A self-contained or ahead-of-time .NET helper also lists the runtime packs of the frameworks it references, and a package's notices for the third-party code it bundles come after its license. The config names where each is described, relative to its own folder:

// notices.config.js
export default {
  out: 'notices/out/THIRD-PARTY-NOTICES.txt',
  title: 'My App — third-party notices',
  npm: { lock: 'package-lock.json', installedAt: '.' },
  cargo: { cwd: 'src-tauri', target: 'x86_64-pc-windows-msvc' }, // the targets the app is built for
  nuget: {
    project: 'helper/Helper.csproj',                                // restored first, needs dotnet
    properties: { RuntimeIdentifier: 'win-x64', SelfContained: true }, // as `dotnet publish` is given them
  },
  pinned: { pins: 'notices/pins.json', dir: 'notices/texts' },
  // render: (packages) => '…',  // a document of your own instead of the default one
}

The .NET project is restored with nuget.properties as MSBuild properties — the ones the helper is published with. A helper published with -r <rid> --self-contained on the command line ships the runtime packs of its frameworks, and only a restore for that runtime identifier, self-contained, downloads them: without the properties they are missing from the notices. Properties that say self-contained (SelfContained or PublishAot) with a restore that downloaded no runtime pack fail, rather than leave the runtime out without a word.

A pin that applies to no shipped package, or a pinned text that is missing or not the one pinned, fails. --strict also fails on a package left without a license text — for a release that goes out to the public; without it, those are listed. --check writes nothing and fails when the file is not what would be written. npx tauri-kit-dev notice-pins --pins notices/pins.json --dir notices/texts downloads the pinned texts and records their SHA-256. npx tauri-kit-dev notice-pins --config notices.config.js first finds a pin for each package still without a text — its license file at the commit it was published from (recorded by cargo publish, a nuspec, or an npm manifest) or else at its release tag, never a branch — adds those, fetches them, and names the packages it could not find one for. Read what it found before committing it. The pieces are exported from @iyulab/tauri-kit-dev/notices (shippedNotices, noticesText, suggestPins, the readers) for a document composed differently.

Checking public text

npx tauri-kit-dev public-text [--config <file>] [--history] [<repo>...]

Checks each git repository (default: the current one) — tracked files, and the messages of commits not yet pushed — for local paths and private hosts. --history also reads every commit message, every line any commit ever added and tag messages: old versions of files stay readable once a repository is public.

--config names a module with the caller's own rules. Keep it out of the repository it checks: a list of the names to keep out of a public repository, committed there, publishes them.

// public-text.config.js
export default {
  forbidden: [{ why: 'internal name', re: /\bproject-codename\b/i }],
  allowed: ['.gitignore:notes/'], // `path:text` substrings known to be fine
  // defaults: false,             // drop the built-in local-path and private-host rules
}

A line that is meant to carry what a rule finds — a test whose subject is a home folder — says so on that line or the one above it, so the exception sits next to its reason:

// public-text: allow — the test is about paths under a home folder
const home = 'C:\Users\someone\notes'

The same check is available as a function:

import { checkRepo, loadConfig } from '@iyulab/tauri-kit-dev/public-text'

const findings = checkRepo('.', { config: await loadConfig('public-text.config.js'), history: true })

Versioning

While the version is 0.x, a minor bump may break the API; a patch bump does not. CHANGELOG.md lists what each release changed.

License

MIT