@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
Install dependencies:
npm ciCopy local secrets:
cp .dev.vars.example .dev.varsFill 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.
Create a D1 database if you are configuring a new Cloudflare account:
npx wrangler d1 create bugdrop-board-devCopy the returned
database_idintowrangler.toml. The Worker binding must remainDB.Apply local D1 migrations:
npx wrangler d1 migrations apply DB --localProvision a board:
npm run provision:board -- --repo mean-weasel/demo --name "Demo Board" --localThe command creates or updates one D1 board for the repo and prints the stable
board.idto use in the embed script.Build the widget bundle:
npm run build:widgetStart the Worker:
npm run devThe development Worker listens on
http://127.0.0.1:8788.Run the local embedded smoke:
npm run test:e2eThe E2E command starts the Worker with local test vars, serves a dummy host app on
http://127.0.0.1:5177, provisions a board throughnpm 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.
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
- Worker URL:
Create the remote D1 database:
npx wrangler d1 create bugdrop-board-prodKeep the binding name as
DBinwrangler.toml, then replace the placeholderdatabase_idwith the id returned by Wrangler. Use the same remote D1 database for migrations, provisioning, deployed API reads, upvotes, and item creation.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 sameBOARD_TOKEN_SECRET,BOARD_TOKEN_AUDIENCE, andBOARD_TOKEN_ISSUERvalues the Worker expects.Set deployed Worker secrets:
npx wrangler secret put BOARD_TOKEN_SECRET npx wrangler secret put GITHUB_ISSUE_ACCESS_TOKENDo not put these values in
wrangler.toml, browser code, or the embed script..dev.varsis only for localwrangler dev.Build and dry-run the Worker bundle:
npm run deploy:checkThis builds
public/board.jsand runswrangler deploy --dry-run, which validates the Worker bundle, assets, and bindings without uploading a deployment.Apply remote D1 migrations:
npx wrangler d1 migrations apply DB --remoteProvision one board for the app's GitHub repo:
npm run provision:board -- --repo mean-weasel/demo --name "Demo Board" --remoteSave the printed
board.id; that value becomes the embed script'sdata-board-id. Running the command again updates the board name and keeps the same repo-backed board id.Deploy the Worker:
npm run deployVerify the deployed surface:
curl https://bugdrop-board.example.workers.dev/health curl -I https://bugdrop-board.example.workers.dev/board.jsThen 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.tomlpoints at its realdatabase_id, and remote migrations have been applied. BOARD_TOKEN_SECRETandGITHUB_ISSUE_ACCESS_TOKENare deployed Worker secrets.ALLOWED_ORIGINSnames 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, andmake checkpass 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.jsCopy 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 exampleboard_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 below500are ignored.data-color: optional accent color for widget controls. Defaults to#2563eb.data-layout: optional layout mode,inlineorpanel. Defaults toinline.data-density: optional density mode,compact,comfortable, orspacious. Defaults tocomfortable.data-config-selector: optional CSS selector for anapplication/jsonconfig element that provides deepercopy,layout,density, andthemecustomization.
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:
headingtitleLabeltitlePlaceholderdescriptionLabeldescriptionPlaceholdersubmitLabelsubmittingLabelloadingLabelemptyLabelerrorTitleretryLabelissuePrefixupvoteLabelupvotedLabel
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:
displayNameemailaud, matchingBOARD_TOKEN_AUDIENCEwhen configured.iss, matchingBOARD_TOKEN_ISSUERwhen 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:
5requests per window; - upvote toggling:
60requests per window; - window size:
60seconds.
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:
ASSETSfrompublic - D1 binding:
DB - Local database name:
bugdrop-board-dev - Worker defaults:
ENVIRONMENT,ALLOWED_ORIGINS,BOARD_TOKEN_AUDIENCE, andBOARD_TOKEN_ISSUER - Request throttling defaults:
REQUEST_THROTTLE_WINDOW_SECONDS,ITEM_CREATE_RATE_LIMIT, andUPVOTE_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_TOKENFor deployed D1 migrations, use:
npx wrangler d1 migrations apply DB --remoteTo check deploy packaging without uploading the Worker, use:
npm run deploy:checkSecret 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_AUDIENCEandBOARD_TOKEN_ISSUERmust match theaudandissclaims the host token endpoint signs.ALLOWED_ORIGINSmust include the host app origins that embed the widget.- The D1 binding must stay
DB, anddatabase_idmust 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:
Generate a new long random value.
Update the host app backend so it signs new board tokens with that value.
Replace the deployed Worker secret:
npx wrangler secret put BOARD_TOKEN_SECRETRestart or redeploy the host app if its runtime requires it.
Ask active users to refresh the host page, or wait for existing short-lived tokens to expire.
Verify the Worker bundle and embedded flow:
npm run deploy:check npm run test:e2eFor 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:
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.
Replace the deployed Worker secret:
npx wrangler secret put GITHUB_ISSUE_ACCESS_TOKENConfirm the board still points at the expected repo:
npm run provision:board -- --repo mean-weasel/demo --name "Demo Board" --remoteVerify the Worker bundle:
npm run deploy:checkFor 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:e2eThe 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_IDCLOUDFLARE_API_TOKEN, scoped to deploy the Worker and manage the configured D1 databaseBOARD_TOKEN_SECRETISSUE_ACCESS_TOKEN, containing the GitHub Issues token. The workflow maps this to the deployed Worker secretGITHUB_ISSUE_ACCESS_TOKEN.
Before the first promotion, update wrangler.toml for the target environment:
ENVIRONMENT = "production"ALLOWED_ORIGINSlists exact host app originsBOARD_TOKEN_AUDIENCEandBOARD_TOKEN_ISSUERmatch the host token endpoint- D1
database_idpoints at the remote D1 database
Run the workflow from GitHub Actions:
- Select Deploy Worker.
- Choose the GitHub Environment, such as
production. - Leave Apply remote D1 migrations enabled unless migrations were already applied.
- Optionally enter
provision_repoasowner/nameandprovision_nameto create or update the board row before deployment. - Optionally enter
smoke_url, such ashttps://bugdrop-board.example.workers.dev, to verify the deployed/healthand/board.jsendpoints after deployment. - Optionally enter
smoke_expect_environmentwhen the WorkerENVIRONMENTvalue differs from the GitHub Environment name. Leave it blank to expect the selected GitHub Environment, such asproduction. - Optionally enter all browser CORS smoke inputs to prove embedded browser access:
smoke_cors_origin,smoke_cors_board_id, andsmoke_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-tokenThen 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.jsscripts/verify-deployed-worker.jsscripts/verify-clean-room-install.jsscripts/verify-clean-room-install-core.jsscripts/verify-package-install.jssrc/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:widgetVerify the package contents locally:
npm run pack:check
make pack-checknpm 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-smokeThe 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:workflowTo 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:
- Select Package Widget.
- Leave Build and verify the npm package without publishing enabled for a package preview.
- Choose
latestornextas the npm dist-tag. - 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 10000The 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-rehearsalThe 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-node24This 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, andISSUE_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 checkCurrent 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.
