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

@fluentui-react-native/storybook-desktop

v0.3.0

Published

CLI and configuration for React Native desktop Storybook applications

Readme

React Native Desktop Storybook

Reusable CLI and configuration for Fluent UI React Native desktop test apps. Together with the companion runtime, it provides:

  • the macOS and Windows Lite UI shell;
  • the Win32 Paper desktop chrome and Callout-backed pop-outs;
  • a Fluent theme toolbar and preview decorator;
  • Metro and Babel configuration for the repo's pnpm-linked desktop hosts; and
  • the standalone Storybook channel and MCP server;
  • generated platform Story Manifests and authenticated runtime readiness; and
  • an embedded W3C Desktop Driver listener in the same server process; and
  • a Commander CLI and matching API for serving, native preparation, bundling, builds, launches, and smoke tests.

The React Native implementation lives in the companion @fluentui-react-native/storybook-desktop-runtime package. Keeping its React and React Native peers out of this package gives the CLI a physical Yarn workspace locator, so storybook-desktop and storybook-server binaries work with Yarn's pnpm linker instead of resolving through an unmaterialized virtual workspace path.

Consuming apps own their native identity in app.json, story globs, generated storybook.requires file, component dependencies, and exceptional platform automation. A root storybook.config.ts declares which packages supply stories and overrides only platform behavior that cannot use the shared defaults:

import {
  createWindowsSmokeOptions,
  createWin32RunCommand,
  createWin32SmokeCommand,
  makeDesktopStorybookConfig,
} from '@fluentui-react-native/storybook-desktop/config';

const win32Host = {
  component: 'MyStorybook',
  windowTitle: 'My Storybook (Win32)',
} as const;

export default makeDesktopStorybookConfig({
  projectRoot: new URL('.', import.meta.url),
  storyPackages: [
    '@scope/components',
    [
      '@scope/native-component',
      {
        platforms: ['macos', 'windows'],
      },
    ],
  ],
  platformOptions: {
    windows: {
      smoke: createWindowsSmokeOptions({
        windowTitle: 'My Storybook',
      }),
    },
    win32: {
      run: createWin32RunCommand(win32Host),
      smoke: {
        command: createWin32SmokeCommand({
          ...win32Host,
          testIDPrefix: 'my-storybook',
        }),
      },
    },
  },
});

The corresponding React Native Test App manifest supplies native identity:

{
  "name": "MyStorybook",
  "displayName": "My Storybook",
  "macos": {
    "bundleIdentifier": "com.example.my-storybook"
  },
  "storybook": {
    "testIDPrefix": "my-storybook"
  }
}

The app's src/main.ts becomes a small adapter:

import config from '../storybook.config.ts';

export default config.getStorybookConfig();

The returned DesktopStorybookConfig resolves package roots lazily and exposes app identity, display name, the custom storybook.testIDPrefix field, package metadata, resolved story packages, and generated story globs for CLI and test orchestration. A config-level testIDPrefix remains available as an explicit override for consumers that do not store Storybook identity in app.json.

The src/config import graph must also load before the TypeScript build: Storybook's prebuild loader falls back to CommonJS-transpiled source when lib is absent. Use explicit .ts extensions for relative imports in that graph; rewriteRelativeImportExtensions converts them to .js in build output.

CLI and API

For a focused authored-test lane within the owned smoke lifecycle, set STORYBOOK_SMOKE_STORY and STORYBOOK_SMOKE_TAG before running storybook smoke --<platform> --mode stories-and-tests. These override WDIO story/tag settings and filter any legacy plans as well. Without overrides, smoke runs configured WDIO cases across all matching stories and legacy desktop-e2e plans. An explicit selector that matches no tests fails instead of reporting a false pass. This retains normal native lease/readiness and process cleanup; it does not change input capability policy.

The storybook-desktop binary loads storybook.config.ts, .mts, .js, .mjs, or .cjs from the current package. Use .mts when the consuming package otherwise defaults JavaScript files to CommonJS. Select a target with a short platform option, or omit it to use FURN_STORYBOOK_PLATFORM and then the host default:

storybook-desktop server --win32
storybook-desktop build-driver --windows
storybook-desktop build-driver --macos
storybook-desktop driver --windows
storybook-desktop manifest --windows
storybook-desktop instance --windows
storybook-desktop prep --macos
storybook-desktop bundle --windows
storybook-desktop build --macos
storybook-desktop run --windows
storybook-desktop smoke --win32 --mode stories
storybook-desktop smoke --windows --mode stories-and-tests

Use --config <path> for a differently named configuration file. build-driver builds only the source-shipped native helper. prep first ensures that helper, then installs CocoaPods on macOS or generates the React Native Test App solution on Windows; Win32 prep now ensures the shared Windows helper. bundle generates the selected story catalog and routes to rnx-cli bundle. build and run route to rnx-cli by default using native project names derived from the app manifest. The config supplies default macOS workspace/scheme and Windows solution arguments from the app key, and reads the macOS bundle identifier directly from app.json. Win32 has no native project to build, so its default build and run operations are unsupported until the consumer provides a prebuilt-host launch command.

Set platformOptions.macos.nativeDriver.macosSigningIdentity to use a stable macOS code-signing identity. When omitted, the source build uses an ad hoc signature and TCC permissions may need to be granted again after a rebuild. The shared standalone macOS run command applies the enlistment-specific xcconfig, matching the bundle identity used by driver and smoke.

server loads the same config, selects the matching platform catalog, and derives the app-owned Storybook config directory automatically. It accepts --host and --port; the separate storybook-server binary is a convenience alias for this subcommand. Consumer package scripts should forward arguments rather than define one server alias per platform. See src/cli/README.md for the recommended minimal scripts and development, E2E, CI, and agent workflows.

manifest statically extracts the selected platform's stories and serializable parameters.desktopDriver plans, then writes exact-platform and portable-plan digests. instance prints the enlistment-specific channel, Metro, and driver identity. driver starts the Storybook channel/MCP server and the W3C Desktop Driver listener on separate loopback ports in one Node process. It resolves the verified native helper before starting Metro and registers a process-backed target. The deterministic fake host remains test-only.

Navigation waits for both an authenticated runtime hello and the first storyRendered event from that same connection. The hello alone can arrive before Storybook installs its navigation handlers, particularly after a native app restart. This initial render only gates startup: each requested story must still acknowledge the matching request/run and pass native story-root verification before its tests execute.

All component catalog tests use executable WDIO functions. The legacy plan runner remains supported for compatibility and is covered by dedicated fixtures. Plan extraction evaluates only the inline static desktopDriver literal and supports TypeScript satisfies; dynamic values fail with source context instead of being omitted.

Executable tests inside stories

Add a top-level wdio property to a CSF3 story. The story is the suite (describe), and named functions are its test cases (it). Test bodies use WebdriverIO directly rather than a custom action/expectation JSON format:

import type { StoryObj } from '@storybook/react-native';
import type { WdioStory } from '@fluentui-react-native/storybook-desktop/testing';

type Story = WdioStory<StoryObj<typeof Button>>;

export const Default: Story = {
  args: { testID: 'save-button' },
  wdio: {
    'is enabled': async ({ browser, expect }) => {
      await expect(await browser.$('~save-button')).toBeEnabled();
    },
    'supports native activation': async ({ browser, platform, skip }) => {
      const features = browser.capabilities['furn:features'];
      if (!features?.physicalClick) {
        skip('Physical pointer input is unavailable.');
        return;
      }
      const button = await browser.$('~save-button');
      await button.click();
      if (platform !== 'macos' && features.focus) {
        await browser.waitUntil(async () => (await button.getProperty('focused')) === true);
      }
    },
  },
};

browser is the real WebdriverIO browser, expect is expect-webdriverio, and desktop exposes the existing typed native assertions and story commands. platform is the target endpoint, typed as 'macos' | 'windows' | 'win32'; it is not process.platform. Win32 stays distinct even though its WebDriver platformName is windows. Native feature flags are typed under browser.capabilities['furn:features']. signal allows cooperative cancellation; skip(reason) records an explicit skip (return from the callback after calling it). Native selectors and supported WebDriver operations apply; there is no DOM or JavaScript execution inside the app.

The original wdio: async (context) => { ... } form remains supported. It is one test, called default for filtering. Named collections must be non-empty, with unique literal names and inline function values; spreads, computed names, methods, nested suites, and dynamic test registration are rejected. This is intentionally a lightweight story-as-suite pattern, not injected Mocha/Jest describe/it globals. Names can be discovered without executing test code, and each case retains its own process and native session.

Use getProperty() to read a native element property before asserting it; Jest's ordinary toHaveProperty() matcher inspects the JavaScript object, not the native control.

Callbacks are extracted without importing React Native in Node. They must be inline functions with no closures over the story module's imports, helpers, args, or render state. Declare test-local values inside the callback. Import Node-compatible helpers with a literal await import('node:assert/strict'), await import('./test-helper.js'), or package specifier inside the callback; these resolve from the original story file. Unsupported expressions fail extraction with source context rather than disappearing.

The shared Babel config removes these callbacks before Metro resolves their dependencies, on all three desktop endpoints. Import WdioStory with import type, never import Node test libraries at story-module scope, and use createDesktopStorybookBabelConfig in the app. Ordinary on-device story rendering and controls remain unchanged.

Run the experiment from the consuming app:

yarn storybook test --macos --list
# Terminal 1: keep the driver supervisor running.
yarn storybook driver --macos
# Terminal 2: launch the native app, then run the inline tests.
yarn storybook run --macos
yarn storybook test --macos --story 'components-button--*'
yarn storybook test --macos --story 'components-button--*' --test '*activation*'

Defaults come from storybook.config.mts; no Mocha, Jest, or WDIO config file is needed:

export default makeDesktopStorybookConfig({
  // ...app identity and story discovery...
  wdio: {
    timeoutMs: 30_000,
    reporter: 'spec', // spec, tap, or dot
    clickMode: 'auto',
    // Optional: story: 'components-button--*', test: '*activation*', tag: 'my-test-tag'
  },
});

--list includes named cases and respects --test filtering. CLI filters, --timeout-ms, --reporter, and --click-mode override those defaults. By default test uses the saved driver's actual port and target, including port-probing adjustments. --url and --target together select an externally managed driver. On macOS the command refreshes an exact, nonce-bound lease for the isolated app identity. Windows and Win32 require the trusted lifecycle owner to provide the existing application lease; the test runner does not attach by an ambiguous process name or title.

Node's built-in test runner executes each test in a separate process. Workers exit naturally so HTTP handles can close; forced test exit can race Windows libuv teardown. The supervisor still bounds and terminates hanging workers and rejects nonzero exits, including exits after a callback reports success. The supervisor owns one attached WebDriver session at a time, authenticates the live manifest, navigates to and remounts the correct story before each callback, and deletes the session after success, failure, timeout, or worker exit. The app and driver remain running. Failed callbacks exit nonzero; no matches is an error. Named cases run in declaration order, grouped by story. The timeout applies per case, not to the entire suite. Assertion failures and timeouts do not prevent other independent cases in that story from running; connection or session-cleanup failures stop execution. Do not share element handles or assume state survives between cases. Reports and best-effort failure source/tree evidence live in artifacts/<platform>/wdio. Generated executable files are removed after the run. Named results include testName, and failure evidence uses distinct paths for each case. Callback digests participate in manifest freshness checks: restart the driver and reload the app after editing tests.

Button's Default replaces its legacy plans with named WDIO tests for semantics, platform-specific focus, pointer activation, and PNG capture. ExternallyDrivenSelection verifies actual activation and caller-owned state updates through the visible status label, since macOS does not expose checked on the native button role. Run callbacks directly with storybook test, or include them in storybook smoke --<platform> --mode stories-and-tests. Smoke groups tests by story ID, runs that story's remaining legacy desktop-e2e plans and selected wdio cases, then advances to the next story. Non-default stories are included. Every test gets a fresh preview; the currently selected sidebar page is not a prerequisite. The aggregate smoke report contains static results in tests and executable results in wdio. Per-story executable reports are under artifacts/<platform>/desktop-driver/wdio.

For legacy JSON plans only, use the consuming app's Desktop Driver CLI:

yarn desktop-driver stories list \
  --url http://127.0.0.1:<driver-port> \
  --target <target-id>

yarn desktop-driver stories run \
  --url http://127.0.0.1:<driver-port> \
  --target <target-id> \
  --tag desktop-e2e \
  --artifacts artifacts/<platform>/desktop-driver

The driver startup output and instance command report the driver port and target identity. WebdriverIO is the sanctioned high-level runner; raw W3C and typed client surfaces remain available for integration and conformance tests.

createWindowsSmokeOptions supplies a package-owned Fabric lifecycle that bundles the Windows catalog, prepares and builds the generated app, registers and launches its Debug package, starts the channel server and Metro, traverses every story, and stops only the processes it recorded. createWin32SmokeCommand bundles and launches the configured REX host, verifies the shared desktop chrome, resize handles, and addon surface through the configured test-ID prefix, traverses every story, and performs the same ownership-safe cleanup. --mode stories is the default renderability gate; --mode stories-and-tests performs the same complete traversal and then runs desktop-e2e plans and configured executable callbacks, grouped by story, through the native provider. Storybook owns app launch and supplies an exact nonce-bound process lease; WebDriver attaches and preserves the app until the Storybook lifecycle performs final cleanup. The reusable macOS lifecycle resolves the launched app by its isolated bundle identifier and atomically records its PID, start time, executable, and nonce before creating the attached WebDriver session. Consumers provide only native identity, title, test-ID prefix, and optional required story IDs. The Windows helper also records React Native Test App's Debug Metro port (8081 by default), while Storybook and Desktop Driver ports remain enlistment-specific. Artifacts are written beneath the consuming app's artifacts/windows or artifacts/win32 directory.

The Windows Fabric lifecycle restarts only its exact owned app after the full catalog traversal, then rewrites the nonce-bound application lease and runs authored tests against the warm Metro bundle. This avoids carrying accumulated Fabric story state into the native test phase while preserving the same server, ports, and process ownership.

smoke can also use a complete consumer command or the generic reusable lifecycle. The generic lifecycle starts the shared channel server and Metro, builds and launches the app, selects every indexed story, runs the configured app stop command, and terminates only the server processes it started. macOS uses the package's bundle-ID-based stop command by default. Other generic platform lifecycles require an explicit smoke.stop, while consumers can replace the complete smoke command when native process ownership needs platform-specific handling. Prefer the package-owned Windows and Win32 command factories over app-local lifecycle scripts.

Each reusable smoke run derives a stable instance ID from the canonical consuming-project root. That ID suffixes the configured macOS bundle identifier and seeds separate Storybook and Desktop Driver ports, with occupied-port probing before launch. Generic and macOS lifecycles also use an enlistment-specific Metro port. The Windows React Native Test App lifecycle reserves its required 8081 Metro port, so two Metro-backed Windows smoke runs cannot execute concurrently. The CLI supplies a generated Xcode configuration containing PRODUCT_BUNDLE_IDENTIFIER and RCT_METRO_PORT; the Metro helper serializes the matching Storybook port into a generated runtime polyfill. Separate enlistments therefore never select or stop one another's app or owned services. Generated instance files live under the consuming app's storybook-desktop.generated and macos/.storybook-desktop directories and should be ignored. The runtime module intentionally uses a visible directory because Metro's Windows file map excludes hidden cache directories.

The same operations are available without Commander:

import { DesktopStorybookCli } from '@fluentui-react-native/storybook-desktop/cli';
import config from './storybook.config.ts';

const storybook = new DesktopStorybookCli(config);
await storybook.bundle('macos');
await storybook.smoke('macos', { mode: 'stories-and-tests' });

Command runners are injectable through the constructor for higher-level automation and tests. DesktopCommand, DesktopPlatformOptions, createDesktopStorybookInstance(), and the related configuration types are exported from the /config subpath. server() runs the foreground server until it is stopped, so supervisors should invoke it as a dedicated task rather than await it before another operation.

Command logs and pipeline failures

The shared command runner writes stdout and stderr continuously to a single log file per command under artifacts/storybook-commands in that command's working directory. Both streams share one descriptor, preserving their write order without holding an entire build log in memory.

Normal output contains a start record with the log path and a short completion summary. A failed command replays its complete combined log between labeled BEGIN/END records, then reports failure on stderr. Replay groups are serialized so concurrently completing commands do not mix their output. Owned background services also replay their logs when smoke fails.

Use the global --verbose option to replay successful-command logs as well:

yarn storybook --verbose test --macos --story 'components-button--*'
yarn storybook --verbose smoke --macos --mode stories-and-tests

Console replay is grouped at command completion; the files update while commands are running, so ongoing logs are available without shell redirection. The verbose setting is inherited by package-owned Windows and Win32 lifecycle subprocesses.

Failures also emit explicit [storybook] FAIL diagnostics identifying the story, test, failed step when available, and execution phase. Static test failures are reported as each test settles, before the next test starts. Inline callback diagnostics are retained independently of the Node reporter, including with dot; CLI boundaries preserve nested causes and aggregate errors. A failed test cannot be hidden by a successful worker exit or by a secondary evidence/cleanup failure.

Archive the command logs alongside the per-platform test artifacts in CI. Logs can contain application output and test data; apply the same access and retention policy as other test evidence. Programmatic callers can inject output and errorOutput streams and set verbose on DesktopStorybookCli or NodeDesktopCommandRunner.

The app integrates its generated Storybook view with the shared runtime:

import { createDesktopStorybookApp } from '@fluentui-react-native/storybook-desktop-runtime';

import { view } from './storybook.requires';

export default createDesktopStorybookApp(view);

Use createDesktopStorybookPreview() from the runtime package in the app's preview.tsx. Metro configuration is exposed from @fluentui-react-native/storybook-desktop-runtime/metro; Babel, server, config, and CLI helpers are exposed from the corresponding @fluentui-react-native/storybook-desktop subpaths.

When launched through driver or the reusable smoke lifecycle, the generated runtime instance supplies the configured test-ID prefix, bridge nonce, target identity, and manifest digests. The runtime exposes stable app/story root markers and correlates each story selection or reset with a run ID and preview generation.