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

@ebay-dt/bonfire-next

v0.1.0

Published

Next.js server integration for Bonfire.

Readme

@ebay-dt/bonfire-next

Next.js integration for Bonfire. Server APIs are exposed from @ebay-dt/bonfire-next/server, and the Next-specific lifecycle client adapter is exposed from @ebay-dt/bonfire-next/client.

It provides HS256 development sessions, the development authentication proxy, login/status/logout handlers, and server-side login-page model composition. Production remains public and does not require GitHub CLI or BF_SESSION_SECRET.

Installation and instance setup

Install @ebay-dt/bonfire-next, @ebay-dt/bonfire-core, and @ebay-dt/bonfire-react in the application. Install @ebay-dt/bonfire-cli as a development dependency.

Create src/bonfire.ts. Keep the application configuration and Bonfire composition together in this server-only entry point:

import {
  createBonfireNext,
  createDeploymentProvider,
} from "@ebay-dt/bonfire-next/server";

const deploymentProvider =
  process.env.NODE_ENV !== "production" &&
  process.env.BONFIRE_DEPLOYMENT_ENDPOINT !== undefined
    ? createDeploymentProvider({
        endpoint: process.env.BONFIRE_DEPLOYMENT_ENDPOINT,
      })
    : undefined;

export const bonfire = createBonfireNext({
  appId: "my-app",
  name: "My App",
  auth: {
    github: {
      hostname: "github.com",
    },
  },
  repositoryRoot: process.cwd(),
  primaryBranch: process.env.BF_PROTOTYPE_BASE_BRANCH,
  deploymentProvider,
  sessionSecret: process.env.BF_SESSION_SECRET,
  disableAutoBranchSwitch: process.env.BF_DISABLE_AUTO_BRANCH_SWITCH,
});

Theme registry configuration

Theme configuration is optional. Add a themes property to the createBonfireNext options; the registry is validated by @ebay-dt/bonfire-core and included in prototype route models:

export const bonfire = createBonfireNext({
  appId: "my-app",
  name: "My App",
  auth: { github: { hostname: "github.com" } },
  themes: {
    definitions: [
      {
        id: "alpha",
        label: "Alpha",
        allowedColorSchemes: ["system", "light", "dark"],
      },
      { id: "beta", label: "Beta", allowedColorSchemes: ["dark"] },
    ],
    defaultTheme: "alpha",
    defaultColorScheme: "system",
  },
  repositoryRoot: process.cwd(),
});

Prototype metadata can provide a validated theme policy. If it is omitted, Bonfire applies the instance defaults and registered capabilities. The server integration passes the normalized policy and registry to the host; it does not scan metadata or construct a theme map in the application. The preferred host composition passes prototype.themeRegistry directly to PrototypeEnvironment alongside prototype.theme.

The host owns theme CSS. Use the provider's data-theme and data-color-scheme attributes in an instance stylesheet. Keep a system selector and a prefers-color-scheme media rule when a theme supports System; Bonfire retains system as the attribute value and exposes the resolved Light or Dark value separately through React.

The deployment endpoint is complete, has no embedded default, and must remain in server-only configuration. When configured, the provider sends only { applicationId, login }; it does not send owner or branch values. The applicationId (normally appId) must exactly match the application's DT deployment-service registration. The service resolves the repository, project, deployment target, and credentials from the application ID and login. HTTP 201 confirms deployment creation, while readyState reports the provider's current state; Bonfire preserves the deploymentId but does not poll it. deploymentAvailable exposes configured development capability to the lifecycle provider, not authorization.

A registration mismatch is intentionally surfaced as a safe deployment failure; Bonfire does not send the provider's response details to the browser. Diagnose it server-side by comparing the configured application ID with the registered value. Any host-facing message should remain generic, such as “Deployment is not configured for this application.”

Run bonfire prepare before development so the secret exists in .env.local. An externally supplied BF_SESSION_SECRET takes precedence. The explicit repository root enables request-time discovery under src/app/(prototypes) without relying on package-internal process.cwd(). BF_PROTOTYPE_BASE_BRANCH can override the default main branch used as the base for identity branches. The configured branch must exist; a missing base branch fails preparation rather than silently using the current branch.

A development instance can also receive a trusted server-only preparePrototypeFiles callback. It runs after Bonfire scaffolds or duplicates the destination and before the initial commit, allowing the host to write prototype-local files. Callback failures clean the newly owned destination; commit or push failures preserve completed output. Do not supply this callback together with a prototypeLifecycle override.

Next host contract

Next.js cannot install filesystem routes for a package, so each application adds these small bindings. Keep authentication logic in the package.

src/app/layout.tsx should own the document and long-lived providers, but not route-specific application chrome. Apply htmlProps to the root <html> element:

import "@ebay-dt/bonfire-react/styles/defaults.css";
import { bonfire } from "@/bonfire";

export default async function RootLayout({ children }: LayoutProps<"/">) {
  const { htmlProps } = await bonfire.auth.getAppLayoutModel();

  return (
    <html lang="en" {...htmlProps}>
      <body>{children}</body>
    </html>
  );
}

getAppLayoutModel() returns a paired set of opaque document props and serializable header props. Spread htmlProps on <html> and headerProps on <AppHeader>; do not omit the document props when rendering the header. Both values come from the same getAppLayoutModel() result. Render AppHeader in the route layout that owns it. Keep the dashboard header in a system layout and the prototype header inside the prototype environment, so the header can consume the environment context:

// src/app/(system)/layout.tsx
const { headerProps } = await bonfire.auth.getAppLayoutModel();
return (
  <>
    <AppHeader {...headerProps} />
    {children}
  </>
);

Prototype environment composition

Use getCurrentRouteModel() from a shared prototype route-group layout when the application enters prototypes from the dashboard and does not support soft navigation between prototypes. The model includes the configured registry when themes are enabled, so the host does not scan metadata or rebuild a themesByPath map:

// src/app/(prototypes)/layout.tsx
import { notFound } from "next/navigation";
import { AppHeader, PrototypeEnvironment } from "@ebay-dt/bonfire-react";
import { openInClaudeCode, openInCursor } from "@/actions/agents";
import { bonfire } from "@/bonfire";

export default async function PrototypeLayout({ children }: LayoutProps<"/">) {
  const [{ headerProps }, prototype] = await Promise.all([
    bonfire.auth.getAppLayoutModel(),
    bonfire.prototypes.getCurrentRouteModel(),
  ]);
  if (prototype === null) notFound();

  const agentActions = { openInClaudeCode, openInCursor };
  const header = <AppHeader {...headerProps} agentActions={agentActions} />;
  if (prototype.theme === undefined)
    return (
      <>
        {header}
        {children}
      </>
    );
  return (
    <PrototypeEnvironment
      prototypeUrl={prototype.url}
      theme={prototype.theme}
      themeRegistry={prototype.themeRegistry}
      agentActions={agentActions}
    >
      {header}
      {children}
    </PrototypeEnvironment>
  );
}

Keep this layout scoped to prototype routes. Dashboard, login, and other application-level pages should remain outside it. If the application later adds soft navigation between prototypes, move the boundary to the route segment that identifies one prototype (for example [login]/[slug]/layout.tsx) and use getRouteModel({ login, slug }) there. That is what allows navigation within one prototype to preserve state while navigation to another remounts it.

Prototype lifecycle composition

The lifecycle-aware dashboard uses the framework-neutral controller and React components from @ebay-dt/bonfire-react/lifecycle. The Next-specific provider supplies router integration, dirty-state observation, and Server Action bindings, while LifecycleProvider owns the React controller lifecycle. The dirty endpoint is a long-lived SSE route and must run on the Node runtime. Bind it directly; do not wrap it in another handler:

// src/app/api/prototype-dirty/route.ts
import { bonfire } from "@/bonfire";

export const runtime = "nodejs";
export const GET = bonfire.prototypes.routes.dirty;

Then mount the provider and lifecycle-aware header from the root layout:

import { NextLifecycleProvider } from "@ebay-dt/bonfire-next/client";
import { AppHeader } from "@ebay-dt/bonfire-react";
import {
  createPrototype,
  deletePrototype,
  discardPrototype,
  duplicatePrototype,
  editPrototype,
  deployPrototype,
  savePrototype,
} from "@/actions/prototypes";
import { bonfire } from "@/bonfire";

const { headerProps } = await bonfire.auth.getAppLayoutModel();

<NextLifecycleProvider
  enabled={process.env.NODE_ENV !== "production"}
  user={headerProps.user}
  deploymentAvailable={bonfire.prototypes.capabilities.deployment}
  dirtyEndpoint="/api/prototype-dirty"
  actions={{
    create: createPrototype,
    duplicate: duplicatePrototype,
    edit: editPrototype,
    delete: deletePrototype,
    save: savePrototype,
    discard: discardPrototype,
    ...(process.env.NODE_ENV !== "production"
      ? { deploy: deployPrototype }
      : {}),
  }}
>
  <AppHeader {...headerProps} />
  {children}
</NextLifecycleProvider>;

Mount it from the root layout so the controller survives dashboard/prototype navigation. Render the regular PrototypeDashboard from the page and pass the resolved user; it consumes lifecycle context directly. Production can leave the provider disabled, in which case the dashboard renders its public read-only view. The action bindings must include create, duplicate, edit, delete, save, discard, and deploy when deployment is enabled. Edit/delete/deploy are authorized by the server-side lifecycle adapter; UI visibility is not an authorization boundary. The provider performs one host refresh after a successful edit or delete.

Agent launch bindings follow the same thin host-action pattern. Add a server-only wrapper module; it must delegate to bonfire.prototypes.agents and must not reproduce authentication, ownership, process, or filesystem logic:

// src/actions/agents.ts
"use server";

import { bonfire } from "@/bonfire";
import type {
  OpenInAgentInput,
  OpenInClaudeCodeInput,
} from "@ebay-dt/bonfire-core";

export async function openInCursor(input: OpenInAgentInput) {
  return bonfire.prototypes.agents.openInCursor(input);
}

export async function openInClaudeCode(input: OpenInClaudeCodeInput) {
  return bonfire.prototypes.agents.openInClaudeCode(input);
}

Pass these Server Action references to both packaged surfaces. The dashboard page can provide them to owner cards:

<PrototypeDashboard
  {...model}
  agentActions={{ openInClaudeCode, openInCursor }}
  user={user}
/>

The root header accepts the same bindings:

<AppHeader {...headerProps} agentActions={{ openInClaudeCode, openInCursor }} />

The card controls are owner-only icon buttons with tooltips. A successful card launch navigates into the selected prototype; preflight and launch failures remain on the current page so their dialogs can be shown. The header controls are text menu rows and close the account popover when clicked. Both surfaces use the validated prototype reference on the server; browser props contain no absolute filesystem path or shell command.

Cursor requires cursor and Claude Code requires claude on the PATH inherited by the Bonfire server process. On macOS, Claude Code also requires osascript. A GUI-launched Node process may have a different PATH from an interactive shell, so restart Bonfire after changing a shell profile. Claude Code terminal preferences support Terminal.app, iTerm2, and Warp. The first launch without a preference presents the packaged terminal-selection dialog; the selected value is written server-side and then used for the launch.

Preferences are stored at <prototypeRoot>/<login>/preferences.json. This is a local preference file, not prototype source, and hosts must ignore it in Git. For the conventional integration prototype root, use:

/src/app/(prototypes)/*/preferences.json

No Claude Code skill, clipboard fallback, copy/paste setup command, or unauthenticated preferences route is required. Production remains public and read-only: it does not construct the local agent service, launch a process, or read or write preferences.

Migration from the Sandbox reference

This flow adapts the Sandbox reference's direct Cursor/Claude Code launching, owner checks, terminal selection, and actionable failure UI. Bonfire keeps those responsibilities in Core, Node, Next adapters, and packaged React controls; it does not copy the reference application's absolute paths, local Next actions, client setup hook, skills, clipboard behavior, or unauthenticated preferences API.

src/app/login/page.tsx:

import { GitHubCliLoginPage } from "@ebay-dt/bonfire-react";
import { bonfire } from "@/bonfire";

type LoginPageProps = {
  searchParams?: Promise<{
    auth?: string;
    code?: string;
    error?: string;
  }>;
};

export default async function LoginPage({ searchParams }: LoginPageProps) {
  const model = await bonfire.auth.getLoginPageModel(searchParams);
  return <GitHubCliLoginPage {...model} />;
}

src/app/auth/login/route.ts:

import { bonfire } from "@/bonfire";

export const runtime = "nodejs";
export const GET = bonfire.auth.routes.login;

src/app/auth/status/route.ts:

import { bonfire } from "@/bonfire";

export const runtime = "nodejs";
export const GET = bonfire.auth.routes.status;

src/app/auth/logout/route.ts:

import { bonfire } from "@/bonfire";

export const POST = bonfire.auth.routes.logout;

src/proxy.ts:

import { bonfire } from "./bonfire";

export const proxy = bonfire.auth.proxy;

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};

Behavior and security

In development, the proxy requires a valid app-specific, HTTP-only session cookie. Login reads the local GitHub CLI identity, and status refreshes the session while device authentication is in progress. Logout clears the cookie and redirects to /login. In production, the proxy passes requests through, login and status return 404, and logout clears stale state before redirecting to /.

The session secret must contain at least 32 UTF-8 bytes. Tokens use HS256 and contain only the validated identity and expiration. Invalid or tampered tokens are treated as unauthenticated. Secrets are not included in login models, errors, logs, or redirects.

The configured GitHub hostname controls both CLI commands and the device activation URL. For GitHub Enterprise, set the hostname in configuration rather than changing the host bindings.

Prototype dashboard

The configured instance exposes read-only catalog methods:

import { PrototypeDashboard } from "@ebay-dt/bonfire-react";
import { bonfire } from "@/bonfire";

type HomeProps = {
  searchParams: Promise<Record<string, string | string[] | undefined>>;
};

export default async function Home({ searchParams }: HomeProps) {
  const [model, user] = await Promise.all([
    bonfire.prototypes.getDashboardModel(searchParams),
    bonfire.auth.getCurrentUser(),
  ]);
  return <PrototypeDashboard {...model} user={user} />;
}

The catalog is discovered at request time from src/app/(prototypes)/<author>/<slug>. Production is public and does not include the development-only mine option. Catalog reads are read-only and do not invoke GitHub CLI, Git, or the network.

Prototype mutation actions

Create, duplicate, save, and discard actions are returned as typed results and do not perform redirects. Bind them from a small host Server Action module. Save and discard accept only a validated target reference and never push changes:

// src/actions/prototypes.ts
"use server";

import type {
  CreatePrototypeInput,
  DuplicatePrototypeInput,
  PrototypeCommandInput,
} from "@ebay-dt/bonfire-core";
import { bonfire } from "@/bonfire";

export async function createPrototype(input: CreatePrototypeInput) {
  return bonfire.prototypes.actions.create(input);
}

export async function duplicatePrototype(input: DuplicatePrototypeInput) {
  return bonfire.prototypes.actions.duplicate(input);
}

export async function savePrototype(input: PrototypeCommandInput) {
  return bonfire.prototypes.actions.save(input);
}

export async function discardPrototype(input: PrototypeCommandInput) {
  return bonfire.prototypes.actions.discard(input);
}

export async function deployPrototype(input: PrototypeCommandInput) {
  return bonfire.prototypes.actions.deploy(input);
}

Keep these wrappers thin: form validation, session lookup, lifecycle errors, and filesystem/Git operations remain in Bonfire. Save creates a local, path-scoped commit and discard restores/removes content only inside that path; neither operation pushes. The host should pass the Server Action references to NextLifecycleProvider from @ebay-dt/bonfire-next/client. In development, the provider protects dirty internal navigation and reload/close attempts; in production it is disabled and the application remains public/read-only.

Supported versions

  • Node.js 24 or newer
  • Next.js 16.2 through 16.x
  • React 19.2 through 19.x
  • ESM-only package output

If gh is missing or unauthenticated, install GitHub CLI and run gh auth login --hostname <hostname>. If device login does not open a browser, use the link and code shown on the login page. The eventual migration of an existing application changes the cookie name and secret contract, so existing sessions will need to authenticate again.