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

@mean-weasel/bugdrop-board

v0.2.0

Published

Embedded, self-hostable ideas/request board.

Readme

bugdrop-board

Embedded, self-hostable ideas/request board backed by Cloudflare D1 and mirrored to GitHub Issues.

Status

Early implementation. The current slice includes a Cloudflare Worker API, D1-backed board state, host-signed user tokens, GitHub Issue creation for new board items, a vanilla TypeScript widget, and a dummy host app used by Playwright.

Product Shape

BugDrop Board is embedded inside an existing app. The host app owns login and serves a short-lived token endpoint for its signed-in users. BugDrop Board verifies those tokens, stores board data in D1, creates one GitHub Issue per board item, and keeps upvotes in D1.

Hosted users should eventually need only GitHub and the embed script. Self-hosters run their own Cloudflare Worker, D1 database, Worker secrets, and GitHub access token.

Local Setup

  1. Install dependencies:

    npm ci
  2. Copy local secrets:

    cp .dev.vars.example .dev.vars
  3. Fill in .dev.vars:

    • BOARD_TOKEN_SECRET: long random value used to verify host-signed board tokens.
    • GITHUB_ISSUE_ACCESS_TOKEN: GitHub token that can create issues in the mirrored repo.
  4. Create a D1 database if you are configuring a new Cloudflare account:

    npx wrangler d1 create bugdrop-board-dev

    Copy the returned database_id into wrangler.toml. The Worker binding must remain DB.

  5. Apply local D1 migrations:

    npx wrangler d1 migrations apply DB --local
  6. Provision a board:

    npm run provision:board -- --repo mean-weasel/demo --name "Demo Board" --local

    The command creates or updates one D1 board for the repo and prints the stable board.id to use in the embed script.

  7. Build the widget bundle:

    npm run build:widget
  8. Start the Worker:

    npm run dev

    The development Worker listens on http://127.0.0.1:8788.

  9. Run the local embedded smoke:

    npm run test:e2e

    The E2E command starts the Worker with local test vars, serves a dummy host app on http://127.0.0.1:5177, provisions a board through npm run provision:board, creates an item, upvotes it, and proves another viewer sees the update through polling.

Production Deploy Readiness

Self-hosters deploy the same Worker and widget bundle that local development uses, but production configuration should be explicit before the first deploy.

  1. Choose the deployed Worker URL and the host app origins that may embed the board.

    Example:

    • Worker URL: https://bugdrop-board.example.workers.dev
    • Host app origins: https://app.example.com, https://admin.example.com
  2. Create the remote D1 database:

    npx wrangler d1 create bugdrop-board-prod

    Keep the binding name as DB in wrangler.toml, then replace the placeholder database_id with the id returned by Wrangler. Use the same remote D1 database for migrations, provisioning, deployed API reads, upvotes, and item creation.

  3. Set deployed non-secret Worker vars in wrangler.toml:

    [vars]
    ENVIRONMENT = "production"
    ALLOWED_ORIGINS = "https://app.example.com,https://admin.example.com"
    BOARD_TOKEN_AUDIENCE = "bugdrop-board"
    BOARD_TOKEN_ISSUER = "your-host-app"

    ALLOWED_ORIGINS="*" is a local development default. For deployed Workers, use exact origins for the app surfaces that will embed the widget. The host token endpoint must sign tokens with the same BOARD_TOKEN_SECRET, BOARD_TOKEN_AUDIENCE, and BOARD_TOKEN_ISSUER values the Worker expects.

  4. Set deployed Worker secrets:

    npx wrangler secret put BOARD_TOKEN_SECRET
    npx wrangler secret put GITHUB_ISSUE_ACCESS_TOKEN

    Do not put these values in wrangler.toml, browser code, or the embed script. .dev.vars is only for local wrangler dev.

  5. Build and dry-run the Worker bundle:

    npm run deploy:check

    This builds public/board.js and runs wrangler deploy --dry-run, which validates the Worker bundle, assets, and bindings without uploading a deployment.

  6. Apply remote D1 migrations:

    npx wrangler d1 migrations apply DB --remote
  7. Provision one board for the app's GitHub repo:

    npm run provision:board -- --repo mean-weasel/demo --name "Demo Board" --remote

    Save the printed board.id; that value becomes the embed script's data-board-id. Running the command again updates the board name and keeps the same repo-backed board id.

  8. Deploy the Worker:

    npm run deploy
  9. Verify the deployed surface:

    curl https://bugdrop-board.example.workers.dev/health
    curl -I https://bugdrop-board.example.workers.dev/board.js

    Then embed the script in a signed-in test page and confirm that a created item appears in the configured GitHub repo and that a second browser session sees the upvote after polling.

Production readiness checklist:

  • Remote D1 database exists, wrangler.toml points at its real database_id, and remote migrations have been applied.
  • BOARD_TOKEN_SECRET and GITHUB_ISSUE_ACCESS_TOKEN are deployed Worker secrets.
  • ALLOWED_ORIGINS names exact host app origins instead of *.
  • Host token endpoint signs short-lived user tokens with matching secret, audience, issuer, and boardId.
  • GitHub access token can create issues in the provisioned board repo.
  • npm run deploy:check, npm run validate, npm run test:e2e, and make check pass before deploy.

Embed Contract

Add the built widget script to a host app page:

<script
  src="https://your-worker.example.com/board.js"
  data-board-id="board_mean_weasel_demo"
  data-api-url="https://your-worker.example.com"
  data-token-endpoint="/api/bugdrop-board-token"
  data-poll-interval="3000"
  data-color="#1f883d"
></script>

For an inline board inside existing page content, provide a mount target and point the script at it:

<section id="feedback-board"></section>
<script
  src="https://your-worker.example.com/board.js"
  data-board-id="board_mean_weasel_demo"
  data-api-url="https://your-worker.example.com"
  data-token-endpoint="/api/bugdrop-board-token"
  data-mount-selector="#feedback-board"
></script>

You can serve board.js from your deployed Worker, or install the npm package and copy or serve the published bundle from one of its equivalent entrypoints:

  • @mean-weasel/bugdrop-board
  • @mean-weasel/bugdrop-board/board
  • @mean-weasel/bugdrop-board/board.js

For npm-based installs, the browser script file is installed at:

node_modules/@mean-weasel/bugdrop-board/public/board.js

Copy that file into your host app's static assets or serve it from your own asset pipeline, then point the script src at the URL where your app serves the copied bundle. The npm package contains the embedded widget bundle only; it does not provision D1, deploy the Worker, or replace the self-host Worker setup above.

Attributes:

  • data-board-id: D1 board id. Current ids are generated from repo owner/name, for example board_mean_weasel_demo.
  • data-api-url: Worker API origin. Defaults to the script origin when omitted.
  • data-token-endpoint: host app endpoint that returns a board token for the current app user.
  • data-mount-selector: optional CSS selector for a host page element that should contain the widget. The widget throws a clear setup error if the selector does not match.
  • data-poll-interval: optional polling interval in milliseconds. Values below 500 are ignored.
  • data-color: optional accent color for widget controls. Defaults to #2563eb.
  • data-layout: optional layout mode, inline or panel. Defaults to inline.
  • data-density: optional density mode, compact, comfortable, or spacious. Defaults to comfortable.
  • data-config-selector: optional CSS selector for an application/json config element that provides deeper copy, layout, density, and theme customization.

The widget runs in an open Shadow DOM root. By default, when the script is in the page body, it inserts its generated root immediately after the script tag, which keeps the board near the install snippet. If the script is outside body content, it falls back to appending to the body. When data-mount-selector is provided, the generated root is appended inside that target element.

Host CSS does not style internals directly. That keeps the embedded board from accidentally breaking when it is installed in a user's app. Use the stable customization contract instead:

<section id="feedback-board"></section>
<script type="application/json" id="bugdrop-board-config">
  {
    "layout": "panel",
    "density": "compact",
    "copy": {
      "heading": "Roadmap queue",
      "titleLabel": "Request",
      "titlePlaceholder": "Short operational request",
      "descriptionLabel": "Business context",
      "descriptionPlaceholder": "Who needs this and why?",
      "submitLabel": "Add request",
      "emptyLabel": "No requests yet.",
      "upvoteLabel": "Prioritize",
      "upvotedLabel": "Prioritized"
    },
    "theme": {
      "accent": "#0f766e",
      "accentSoft": "#ccfbf1",
      "background": "#ffffff",
      "border": "#cbd5e1",
      "buttonRadius": "4px",
      "fieldRadius": "4px",
      "fontSize": "13px",
      "headingSize": "18px",
      "itemRadius": "4px",
      "maxWidth": "640px",
      "muted": "#475569",
      "radius": "4px",
      "surfaceAlt": "#f8fafc",
      "text": "#0f172a"
    }
  }
</script>
<script
  src="https://your-worker.example.com/board.js"
  data-board-id="board_mean_weasel_demo"
  data-api-url="https://your-worker.example.com"
  data-token-endpoint="/api/bugdrop-board-token"
  data-mount-selector="#feedback-board"
  data-config-selector="#bugdrop-board-config"
></script>

data-color remains the easiest accent path for script-tag installs and maps to theme.accent. Existing embeds do not need to add data-config-selector.

Stable copy keys:

  • heading
  • titleLabel
  • titlePlaceholder
  • descriptionLabel
  • descriptionPlaceholder
  • submitLabel
  • submittingLabel
  • loadingLabel
  • emptyLabel
  • errorTitle
  • retryLabel
  • issuePrefix
  • upvoteLabel
  • upvotedLabel

Stable theme keys:

[data-bugdrop-board-root] {
  --bugdrop-board-accent: #1f883d;
  --bugdrop-board-accent-text: #ffffff;
  --bugdrop-board-accent-soft: #e6f4ea;
  --bugdrop-board-background: transparent;
  --bugdrop-board-surface: #ffffff;
  --bugdrop-board-surface-alt: #f6f8fa;
  --bugdrop-board-text: #172026;
  --bugdrop-board-muted: #57606a;
  --bugdrop-board-border: #d0d7de;
  --bugdrop-board-danger: #b42318;
  --bugdrop-board-focus: #0969da;
  --bugdrop-board-font-family: Inter, ui-sans-serif, system-ui, sans-serif;
  --bugdrop-board-font-size: 14px;
  --bugdrop-board-heading-size: 20px;
  --bugdrop-board-line-height: 1.4;
  --bugdrop-board-max-width: 760px;
  --bugdrop-board-radius: 8px;
  --bugdrop-board-item-radius: 8px;
  --bugdrop-board-field-radius: 6px;
  --bugdrop-board-button-radius: 6px;
  --bugdrop-board-border-width: 1px;
  --bugdrop-board-gap: 10px;
  --bugdrop-board-padding: 0;
  --bugdrop-board-item-padding: 12px;
  --bugdrop-board-field-padding: 8px 10px;
  --bugdrop-board-button-padding: 8px 10px;
  --bugdrop-board-shadow: none;
  --bugdrop-board-item-shadow: none;
  --bugdrop-board-button-background: #1f883d;
  --bugdrop-board-button-text: #ffffff;
  --bugdrop-board-button-border: transparent;
  --bugdrop-board-upvote-background: #1f883d;
  --bugdrop-board-upvote-text: #ffffff;
  --bugdrop-board-upvote-border: transparent;
  --bugdrop-board-field-background: #ffffff;
  --bugdrop-board-field-text: #172026;
}

The JSON theme object uses the camelCase names above without the --bugdrop-board- prefix. For example, buttonRadius maps to --bugdrop-board-button-radius. Values are only applied for known keys and are ignored if they contain stylesheet-breaking characters such as {, }, <, >, or ;.

Customization Examples

Compact SaaS:

{
  "layout": "panel",
  "density": "compact",
  "copy": {
    "heading": "Roadmap queue",
    "submitLabel": "Add request",
    "upvoteLabel": "Prioritize",
    "upvotedLabel": "Prioritized"
  },
  "theme": {
    "accent": "#0f766e",
    "border": "#cbd5e1",
    "radius": "4px",
    "itemRadius": "4px",
    "maxWidth": "640px"
  }
}

Soft community:

{
  "layout": "panel",
  "density": "comfortable",
  "copy": {
    "heading": "Community ideas",
    "submitLabel": "Share idea",
    "issuePrefix": "Tracked as #",
    "upvoteLabel": "Cheer",
    "upvotedLabel": "Cheered"
  },
  "theme": {
    "accent": "#9f1239",
    "accentSoft": "#ffe4e6",
    "background": "#fffaf5",
    "fontFamily": "Georgia, serif",
    "itemRadius": "16px",
    "shadow": "0 18px 50px rgba(79, 46, 19, 0.12)"
  }
}

High contrast:

{
  "layout": "panel",
  "density": "spacious",
  "copy": {
    "heading": "Accessibility requests",
    "submitLabel": "Submit access request",
    "retryLabel": "Try loading again",
    "upvoteLabel": "Support",
    "upvotedLabel": "Supported"
  },
  "theme": {
    "accent": "#ffd400",
    "accentText": "#000000",
    "background": "#000000",
    "border": "#ffffff",
    "borderWidth": "2px",
    "fieldBackground": "#000000",
    "fieldText": "#ffffff",
    "focus": "#00ffff",
    "surface": "#000000",
    "text": "#ffffff"
  }
}

Host Token Endpoint

The host app endpoint must return JSON:

{ "token": "payload.signature" }

The token is an HMAC-SHA256 signature over a base64url JSON payload. Required claims:

  • boardId: board id the user may access.
  • externalUserId: stable user id from the host app.
  • exp: expiry time as Unix seconds.

Optional claims:

  • displayName
  • email
  • aud, matching BOARD_TOKEN_AUDIENCE when configured.
  • iss, matching BOARD_TOKEN_ISSUER when configured.

Never expose BOARD_TOKEN_SECRET to browser code. Sign tokens in the host app backend. The dummy host fixture in e2e/fixtures/host-app.ts shows the local test shape.

GitHub Mirroring

For normal development and deployed use, set GITHUB_ISSUE_ACCESS_TOKEN as a Worker secret or in local .dev.vars. The token must be able to create issues in the repo represented by the board.

When a board item is created, BugDrop Board creates a GitHub Issue first. If GitHub issue creation fails, the D1 board item is not stored.

Request Throttling

BugDrop Board includes D1-backed write throttling for the embedded board APIs. The default limits are per board, per signed host user, per action:

  • item creation: 5 requests per window;
  • upvote toggling: 60 requests per window;
  • window size: 60 seconds.

Configure these non-secret Worker vars in wrangler.toml:

REQUEST_THROTTLE_WINDOW_SECONDS = "60"
ITEM_CREATE_RATE_LIMIT = "5"
UPVOTE_RATE_LIMIT = "60"

When a user exceeds a write limit, the API returns 429 with a Retry-After header and does not run the write side effect. Item creation is throttled before GitHub Issue creation, so over-limit requests do not create GitHub Issues.

Use positive integer values. Invalid or missing values fall back to the safe defaults above. Higher limits are useful for trusted internal testing; lower limits are useful for public boards that need more conservative write protection.

Cloudflare Configuration

wrangler.toml defines:

  • Worker entrypoint: src/index.ts
  • Static asset binding: ASSETS from public
  • D1 binding: DB
  • Local database name: bugdrop-board-dev
  • Worker defaults: ENVIRONMENT, ALLOWED_ORIGINS, BOARD_TOKEN_AUDIENCE, and BOARD_TOKEN_ISSUER
  • Request throttling defaults: REQUEST_THROTTLE_WINDOW_SECONDS, ITEM_CREATE_RATE_LIMIT, and UPVOTE_RATE_LIMIT

Keep secrets out of wrangler.toml. For local development, use .dev.vars. For deployed environments, set secrets with:

npx wrangler secret put BOARD_TOKEN_SECRET
npx wrangler secret put GITHUB_ISSUE_ACCESS_TOKEN

For deployed D1 migrations, use:

npx wrangler d1 migrations apply DB --remote

To check deploy packaging without uploading the Worker, use:

npm run deploy:check

Secret Rotation And Recovery

BugDrop Board has two self-host secrets:

  • BOARD_TOKEN_SECRET: shared by the host app backend and the Worker to sign and verify short-lived board tokens.
  • GITHUB_ISSUE_ACCESS_TOKEN: used by the Worker to create GitHub Issues for new board items.

These nearby settings are not secrets, but they matter during recovery:

  • BOARD_TOKEN_AUDIENCE and BOARD_TOKEN_ISSUER must match the aud and iss claims the host token endpoint signs.
  • ALLOWED_ORIGINS must include the host app origins that embed the widget.
  • The D1 binding must stay DB, and database_id must point at the database that contains the provisioned board rows.

Rotate BOARD_TOKEN_SECRET when the signing secret may be exposed or as part of operator policy:

  1. Generate a new long random value.

  2. Update the host app backend so it signs new board tokens with that value.

  3. Replace the deployed Worker secret:

    npx wrangler secret put BOARD_TOKEN_SECRET
  4. Restart or redeploy the host app if its runtime requires it.

  5. Ask active users to refresh the host page, or wait for existing short-lived tokens to expire.

  6. Verify the Worker bundle and embedded flow:

    npm run deploy:check
    npm run test:e2e

    For deployed verification, open a signed-in host page, create a board item, and confirm a second viewer can read/upvote it.

Expected impact: tokens signed with the old secret fail with Invalid board token after the Worker uses the new secret. That is expected until the host app issues fresh tokens. If all users get token errors after the rotation, confirm the host app and Worker use the same secret and that BOARD_TOKEN_AUDIENCE and BOARD_TOKEN_ISSUER still match the host claims.

Rollback: put the previous value back with npx wrangler secret put BOARD_TOKEN_SECRET, then restore the host app signer to the same previous value.

Rotate GITHUB_ISSUE_ACCESS_TOKEN when the GitHub token may be exposed, expires, or repo access changes:

  1. Create a replacement GitHub token that can create issues in the repo used by the provisioned board. For fine-grained tokens, give the target repo issue-write access.

  2. Replace the deployed Worker secret:

    npx wrangler secret put GITHUB_ISSUE_ACCESS_TOKEN
  3. Confirm the board still points at the expected repo:

    npm run provision:board -- --repo mean-weasel/demo --name "Demo Board" --remote
  4. Verify the Worker bundle:

    npm run deploy:check
  5. For deployed verification, create a test item from the embedded board and confirm the matching GitHub Issue appears in the configured repo.

Expected impact: item creation returns GitHub issue creator is not configured when the secret is missing, or Failed to create GitHub issue when GitHub rejects the token. In both cases, the board item is not stored because GitHub Issue creation happens before D1 item persistence.

Rollback: put the previous GitHub token back with npx wrangler secret put GITHUB_ISSUE_ACCESS_TOKEN, then create a test item to confirm GitHub mirroring works again.

Local recovery uses .dev.vars instead of deployed Worker secrets. Update .dev.vars, restart wrangler dev, and rerun:

npm run test:e2e

The local E2E flow uses a fake GitHub Issue creator, so it proves the board/token/widget path but not a live GitHub token. Use a deployed test item to prove real GitHub token recovery.

Environment Promotion

The Deploy Worker GitHub Actions workflow provides a manual self-host promotion path. It does not publish the embed package or create hosted-control-plane resources.

Create one GitHub Environment for each deployment target, for example production. Add these Environment secrets:

  • CLOUDFLARE_ACCOUNT_ID
  • CLOUDFLARE_API_TOKEN, scoped to deploy the Worker and manage the configured D1 database
  • BOARD_TOKEN_SECRET
  • ISSUE_ACCESS_TOKEN, containing the GitHub Issues token. The workflow maps this to the deployed Worker secret GITHUB_ISSUE_ACCESS_TOKEN.

Before the first promotion, update wrangler.toml for the target environment:

  • ENVIRONMENT = "production"
  • ALLOWED_ORIGINS lists exact host app origins
  • BOARD_TOKEN_AUDIENCE and BOARD_TOKEN_ISSUER match the host token endpoint
  • D1 database_id points at the remote D1 database

Run the workflow from GitHub Actions:

  1. Select Deploy Worker.
  2. Choose the GitHub Environment, such as production.
  3. Leave Apply remote D1 migrations enabled unless migrations were already applied.
  4. Optionally enter provision_repo as owner/name and provision_name to create or update the board row before deployment.
  5. Optionally enter smoke_url, such as https://bugdrop-board.example.workers.dev, to verify the deployed /health and /board.js endpoints after deployment.
  6. Optionally enter smoke_expect_environment when the Worker ENVIRONMENT value differs from the GitHub Environment name. Leave it blank to expect the selected GitHub Environment, such as production.
  7. Optionally enter all browser CORS smoke inputs to prove embedded browser access: smoke_cors_origin, smoke_cors_board_id, and smoke_cors_token_endpoint.

The workflow runs:

npm run validate
npm run build:widget
npx wrangler deploy --dry-run [--env staging]
npx wrangler d1 migrations apply DB --remote
npm run provision:board -- --repo owner/name --remote
npx wrangler deploy --secrets-file .deploy.secrets
npm run deploy:smoke -- --url https://bugdrop-board.example.workers.dev --expect-environment production [--cors-origin https://app.example.com --cors-board-id board_owner_repo --cors-token-endpoint https://app.example.com/api/board-token]

The secrets file is generated inside the workflow runner and removed at the end of the job. Do not commit .deploy.secrets.

After promotion, verify the deployed Worker:

curl https://bugdrop-board.example.workers.dev/health
curl -I https://bugdrop-board.example.workers.dev/board.js
npm run deploy:smoke -- --url https://bugdrop-board.example.workers.dev --expect-environment production
DEPLOY_SMOKE_URL=https://bugdrop-board.example.workers.dev \
  DEPLOY_SMOKE_EXPECT_ENVIRONMENT=production \
  make deploy-smoke
npm run deploy:smoke -- \
  --url https://bugdrop-board.example.workers.dev \
  --expect-environment production \
  --cors-origin https://app.example.com \
  --cors-board-id board_owner_repo \
  --cors-token-endpoint https://app.example.com/api/board-token

Then open a signed-in host app page, create a test item, confirm the matching GitHub Issue appears, and confirm another viewer can read or upvote the item.

Rollback is operator-controlled: rerun the workflow from the previous known-good commit or restore the previous Worker secrets with wrangler secret put, then run the deployed smoke checks again.

Embed Package Publishing

The npm package publishes the versioned embed script and source needed to inspect or rebuild it. It does not deploy a Worker, create a CDN release, provision D1, or replace the self-host deployment flow above.

The package entrypoints are:

  • @mean-weasel/bugdrop-board
  • @mean-weasel/bugdrop-board/board
  • @mean-weasel/bugdrop-board/board.js

All entrypoints resolve to public/board.js. The npm package includes:

  • public/board.js
  • scripts/verify-deployed-worker.js
  • scripts/verify-clean-room-install.js
  • scripts/verify-clean-room-install-core.js
  • scripts/verify-package-install.js
  • src/widget/
  • README.md
  • package metadata

Before publishing, bump package.json in a normal PR. The widget build reads that package version by default and embeds it as the runtime __BUGDROP_BOARD_VERSION__ value. For one-off local builds, override it with:

VERSION=0.1.0 npm run build:widget

Verify the package contents locally:

npm run pack:check
make pack-check

npm run pack:check runs npm pack --dry-run. The prepack lifecycle rebuilds public/board.js first, so the tarball preview proves the package would contain the current embed bundle for the package version.

After a publish, verify the registry artifact by installing it into a temporary project:

npm run release:smoke
npm run release:smoke -- --version 0.2.0
npm run install:smoke -- --version 0.2.0
make release-smoke

The smoke command resolves all public package entrypoints, verifies they point at the installed public/board.js bundle, and checks the bundle has the expected widget and fetch code. The package workflow runs this smoke automatically after a non-dry-run publish with a longer retry window to allow npm registry propagation.

npm run install:smoke goes one step further: it installs the published package into a temporary project, serves only the installed public/board.js, loads a minimal host page in Chromium with the documented script attributes, mocks the token/items API responses, and verifies the board mounts inside data-mount-selector.

The Install Smoke GitHub Actions workflow exposes the same clean-room check as a manual, no-secret proof. Dispatch it with a published package version or dist-tag, such as 0.2.0 or latest, when you want GitHub Actions to verify the installable artifact without running npm publish, Cloudflare deploy, or any production credentials. To verify the workflow contract locally, run:

npm run install:smoke:workflow

To verify the package from a completely separate project, run:

tmpdir=$(mktemp -d)
cd "$tmpdir"
npm init -y
npm install @mean-weasel/[email protected]
test -f node_modules/@mean-weasel/bugdrop-board/public/board.js
node -e "require.resolve('@mean-weasel/bugdrop-board/board.js'); require.resolve('@mean-weasel/bugdrop-board/board')"

This proves the published package can be installed without this repository checkout and that the documented static bundle path exists.

The Package Widget GitHub Actions workflow is manually dispatched and dry-runs by default:

  1. Select Package Widget.
  2. Leave Build and verify the npm package without publishing enabled for a package preview.
  3. Choose latest or next as the npm dist-tag.
  4. To publish, rerun with dry-run disabled after the version PR has merged.

Publishing requires a repository secret named NPM_TOKEN. Create a granular npm token with read/write access to the @mean-weasel package scope and no organization-management access. The workflow runs:

npm run validate
npm run pack:check
npm publish --access public --tag "$NPM_TAG"
npm run release:smoke -- --retries 30 --retry-delay-ms 10000

The first public package is published as @mean-weasel/[email protected]. Actual publishing of future versions still requires npm ownership or publish rights for the configured package scope. When in doubt, keep the workflow in dry-run mode and inspect the package file list before publishing.

Release Rehearsal

Before configuring production credentials, run the local release rehearsal:

npm run release:rehearsal
make release-rehearsal

The rehearsal uses only local/test configuration and runs:

npm run provision:board -- --repo mean-weasel/release-rehearsal --name "Release Rehearsal" --local
npm run pack:check
npm run deploy:check
npm run test:e2e
npm run validate
npm run knip
npm run audit
npm run check:actions-node24

This proves the local D1 provisioning path, package dry-run, Worker deploy dry-run, embedded widget smoke, unit/type/lint checks, knip, critical audit, and GitHub Actions version guard without requiring Cloudflare or npm production credentials.

After the local rehearsal passes, configure credentials in GitHub:

  • GitHub Environment secrets for Deploy Worker: CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN, BOARD_TOKEN_SECRET, and ISSUE_ACCESS_TOKEN.
  • Repository secret for Package Widget: NPM_TOKEN.

Then run the GitHub workflows in dry-run or test mode before production:

  • Run Package Widget with dry-run enabled and inspect the package file list.
  • Run Deploy Worker against a staging or test Cloudflare environment, with remote migrations and a disposable board repo when possible.
  • Embed the staging Worker in a signed-in host-app page and confirm item creation, GitHub Issue mirroring, upvoting, and polling from a second session.

For the full staging sequence, use Staging Dogfood. For the real bugdrop.dev embedded-host proof, use Production Dogfood.

Do not publish to npm or deploy to production until the version, npm package ownership, Cloudflare account, GitHub token scope, host app origins, and token issuer/audience values are final.

Verification

Run the standard checks before handing off changes:

npm run release:rehearsal
npm run provision:board -- --repo mean-weasel/demo --name "Demo Board" --local
npm run build:widget
npm run pack:check
npm run deploy:check
npm run deploy:smoke -- \
  --url https://board.bugdrop.dev \
  --expect-environment production \
  --cors-origin https://bugdrop.dev \
  --cors-board-id board_mean_weasel_bugdrop_board_production_dogfood \
  --cors-token-endpoint "https://bugdrop.dev/api/bugdrop-board-token?viewer=a"
DEPLOY_SMOKE_URL=https://board.bugdrop.dev DEPLOY_SMOKE_EXPECT_ENVIRONMENT=production make deploy-smoke
npm run test:e2e
npm run validate
make check

Current Handoff Notes

This repository is still an early vertical slice. The conveyor PR stack has landed on main, @mean-weasel/[email protected] is the currently published npm latest, and the production Worker is available at https://board.bugdrop.dev.

The customization-capable Worker is already dogfooded in production, and package metadata is being prepared for 0.2.0. Do not run the non-dry-run Package Widget workflow until the 0.2.0 version PR has merged, a main-branch package dry-run passes, and the maintainer explicitly approves publishing @mean-weasel/[email protected] to npm.

Remaining release actions are operational: keep running the local release rehearsal before significant changes, run the GitHub workflows against staging/test credentials when changing deploy or package release paths, dogfood the embedded widget in the real signed-token host app at https://bugdrop.dev against the board Worker at https://board.bugdrop.dev, and keep publish approval explicit before any future npm publish.