Repository navigation
Adopt the CodeLit Platform and migrate the workspace to Bun - #81
Merged
Merged
Conversation
Define OSS and cloud billing modes with provider-backed catalogs, subscriptions, checkout, plan changes, webhooks, reconciliation, usage reservations, fair-use controls, domain verification, and billing operations tooling. Enforce organization plan limits across REST, MCP, and workers, update the Organizations billing UI and account messaging, and document pricing, errors, provisioning, and acceptable-use policies.
Configure runtime telemetry, user identification, session recording, and logout resets with tests and environment documentation.
app.sendlit.app runs subscriptions-using-billing, so its billing code and 0005_billing migration are the production baseline. main's 0005_equal_patriot (esp_feedback_connections.subscription_confirmed_at) also shipped, in v0.0.0, with a later journal timestamp, which would make the Drizzle migrator skip 0005_billing on those databases. - Keep 0005_billing exactly as production applied it. - Replace 0005_equal_patriot with 0006_esp_feedback_subscription_confirmed, which replays the guarded 0005_billing statements and adds the column with IF NOT EXISTS. It is a no-op on whichever half a database already has. - Re-apply main's new column in schema-core.ts (the branch split schema.ts). - Use the branch's BOOTSTRAP_* variables and keep main's SUPPRESSION_HASH_KEY. Verified with PGlite from a fresh database, a production-like database (0000-0005_billing) and a v0.0.0 database (0000-0005_equal_patriot): all three converge on the same schema.
Follow the CodeLit Platform toolchain: one Bun 1.4.1 workspace and bun.lock, the API running its TypeScript directly on Bun, and Bun-based images and CI. - Root package.json carries workspaces, trustedDependencies and overrides; pnpm-workspace.yaml and pnpm-lock.yaml are gone. - apps/api runs with `bun src/index.ts` (no tsc emit); tsconfig uses bundler resolution with noEmit and covers tests, so tsconfig.test.json is gone. Bun loads .env itself, so the dotenv calls are removed. The Compose init service and health check use bun. - @sendlit/api-contract exports its TypeScript source like the Platform template's contract package, so nothing has to build it first. The web and docs apps transpile it; email-blocks imports its one type from a schema subpath so the bundled types stay portable. - The API image builds email-editor/email-blocks, then runs on oven/bun with a production install. The web image builds with Bun and runs the standalone server on Node. - CI uses oven-sh/setup-bun with frozen installs and now typechecks. Dependabot groups @codelitdev/* updates. - ci:publish resolves workspace: ranges before changeset publish, as MediaLit does. - Bun resolved ranges afresh, so pin what broke: next 16.3.6, react 19.2.8 (the preset versions), lucide-react ^0.577 to share fumadocs' copy, mdast-util-to-markdown 2.1.2 (2.2.0 recurses in fumadocs' stringifier) and @types/express 4.17.25. - TypeScript 5.9.3 everywhere. - apps/docs/.source is generated on install; stop tracking it.
Lint and format with Biome 2.5.12 using the CodeLit Platform template config, plus SendLit ignores (generated docs source, drizzle SQL, managed Platform files, SVG assets) and an override for the editor packages' Tailwind 3 CSS directives. - Root scripts: lint (biome lint .), format, format:check. The pre-commit hook runs `bun run lint`, as in Platform; CI runs lint and format:check. - Remove eslint.config.cjs, the editor packages' .eslintrc.js, .prettierrc, .prettierignore, lint-staged and their dependencies. - Fix what the recommended rules report without changing behavior: import type, node: protocol, explicit let types, a useless rethrow, unused variables, Number.isNaN on parsed numbers, button types on editor buttons, stable keys where the data has one. Remove two dialogs nothing rendered (EspFormDialog, OrganizationEspFormDialog). - Convert eslint-disable comments for react-hooks/exhaustive-deps into biome-ignore suppressions with the reason. Other suppressions keep shadcn markup, <img> for arbitrary MediaLit hosts, and document.cookie as is. The repository-wide reformat follows in a separate commit.
Run `biome check --write --linter-enabled=false .`: Biome formatting (two-space indent, 88 columns) and import sorting. No other changes.
The CodeLit Platform runs tests with `bun test`. Every workspace now uses `bun test --parallel`, which isolates each file (fresh globals and module registry) the way Vitest did. Test counts are unchanged in every file: api 567, web 68, sendlit 68, email-editor 12, email-blocks 8. - Test files import from bun:test. Bun does not hoist module mocks, so each file registers mock.module() first and loads the code under test with `await import()`, keeping import-time side effects (db client, Redis, BullMQ) out of tests as before. - New private @sendlit/test-utils fills the gaps in Bun's Vitest API: stubEnv/stubGlobal and their resets, waitFor, resetModules, and a happy-dom registerDom() for the React component tests. - apps/api: bunfig.toml preloads src/test/setup.ts; PGlite migrations resolve from import.meta.dir. - getBillingEngine() skipped its cache only under Vitest's VITEST variable; check NODE_ENV=test, which bun test sets, instead. - tsup builds of the editor packages no longer pick up test files. - Remove Vitest, its configs, and the unused jsdom dependency in apps/web.
Every @codelitdev package moves to 0.4.1, which all Platform packages now share. External dependencies use the exact versions in platform-cli's preset, so `platform-cli doctor` reports no issues: better-auth and its OAuth provider 1.7.7, express 4.21.2, pg 8.23.0, pino 9.6.0, drizzle-orm 0.45.2, drizzle-kit 0.31.10, PGlite 0.5.8, ts-rest 3.52.1, zod 3.25.76 and dodopayments 2.48.0. apps/api also depends on @codelitdev/platform, observability and mcp-server-kit, and on platform-conformance for tests. Better Auth 1.7.7 checks the database schema at runtime: - 0007_better_auth_1_7_7 makes account.issuer nullable (1.7.7 no longer writes it; drop it in a later release) and adds the oauth_access_token.revoked column the OAuth provider has always declared. - oauth-server-kit 0.4.1 allows registering every valid audience; keep dynamic registration limited to the MCP resource SendLit seeds. billing.generated.ts is regenerated by codelit-billing 0.4.1. The schema is unchanged from alpha.3; only the generator's formatting differs.
The API now composes the published Platform packages instead of SendLit's copies. This commit builds together with the next one, which moves billing onto the same packages (index.ts already wires its lifecycle). Kernel (@codelitdev/platform): - The Express app moves to createApp() (src/app.ts); index.ts starts workers and loops, serves /ready from readinessReport (startup, shutdown, Postgres, Redis), and on SIGTERM runs one ordered graceful shutdown: stop HTTP, let BullMQ workers finish their job, close queues, Postgres and telemetry. /health uses healthReport; every response carries X-Request-ID. - Requests accept exactly one credential (Platform ADR 0001), using the kernel's extraction plus x-sendlit-apikey and the legacy body key; X-API-Key now works too. Several credentials return 400 credential_ambiguous, a malformed Authorization header is rejected, and nothing falls back. MCP does not treat a browser session as a credential. OAuth tokens are verified by oauth-server-kit's verifyOAuthAccessToken; the team_id claim is read from the verified token and membership is still re-checked. Observability (@codelitdev/observability): - One instance carries pino logs, error capture and OTLP log shipping to PostHog, as in MediaLit. Product events use a second instance with dedupe and per-source caps off, since the package would otherwise drop all but the first email_sent per team per minute. posthog-node, pino-opentelemetry- transport and POSTHOG_LOG_LEVEL are gone; unhandled rejections are captured. - The web app's browser PostHog uses createBrowserObservability. Conformance: - .codelit-platform.json and the managed platform-conformance workflow come from `[email protected] sync`; doctor reports no issues. - test:conformance runs Platform's MCP discovery suite against the real app, SendLit's kernel-rule checks, and the MCP/REST parity manifest (packages/api-contract/src/mcp-parity.ts, validated with mcp-server-kit). - check:drift runs codelit-billing generate --check and compares Better Auth's expected schema with the Drizzle tables. - MCP keeps SendLit's SDK 2 stateless server; mcp-server-kit's transport and CORS are built on SDK 1 sessions (see docs/decisions/0001). OpenAPI generation moves into @sendlit/api-contract/openapi so the API and the docs site render one document; it documents X-API-Key and the credential_ambiguous response. AGENTS.md, the README, the API README and the developer docs describe the Platform conventions, and ADR 0001 records the decisions.
…adapters app.sendlit.app runs this billing code; it moves from billing 0.1.0-alpha.3 to 0.4.1 and the @codelitdev/platform/billing adapters (ADR 0013), following MediaLit's adoption. The generated schema is unchanged. - Action grants come from createBillingActionGrants with the Better Auth verification store. A second grants instance without the recent-auth limit keeps resuming checkout for a pending_payment organization working. Engine action tokens are bound to the organization and its owner when issued. - Sensitive values use the shared AES-GCM port (same key, previous-key and ciphertext format, so stored values stay readable); return URLs use createReturnUrlValidator. - Paid access comes from commercialState(), inside the caller's transaction when reserving quota (subscriptions are locked first). SendLit's own reconciliation sweeps, expiry checks, second webhook projector and the plan_states.plan copy are removed; the column stays until a later release. - Webhooks go through handleBillingWebhook at /webhooks/billing/:provider (Dodo's URL is unchanged). Storage failures now return 503 so Dodo retries. - startBillingMaintenance runs startBillingWorkers plus SendLit's own loops and stops cleanly on shutdown. Worker IDs include the hostname. - product-effects never reactivates an abandoned, closed or suspended organization on a late payment, and releases the trial claim when a checkout is abandoned. - Cancellation now keeps access until the end of the paid period. New POST .../billing/cancel and .../billing/resume routes, paidThroughAt in the billing response and get_plan_usage, and Resume / Cancel plan in the dashboard. - Optional DODO_BRAND_ID for a shared Dodo business. - The operator CLI uses createOperations and adds health, inspect-org and reconcile-cancellations. Run reconcile-cancellations against production before switching traffic to this release (apps/api/README.md).
Bun's filtered installs need the manifest of every workspace a package depends on, including the dev-only @sendlit/test-utils. The web image also type-checks test files during next build, so it gets the test-utils source and apps/web declares @types/bun itself.
- Read allowed origins from `DEV_ALLOWED_ORIGINS`
- Add organization plan policy and trial claim tables with data migration - Remove SendLit-specific columns from canonical billing schema
9 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Testing