20 Commits
Author SHA1 Message Date
saltboandClaude Opus 4.8 53ca110218 feat(about): split runtime into runtime engine + deployment platform
Flatten the instance `runtime` object into two fields: `runtime` (the JS engine,
node | workerd) and `platform` (the deployment host). The About page shows each
as its own row with friendly labels (e.g. "workerd" + "Cloudflare Workers",
"Node.js" + "Docker").

- Detect the platform from the entry file (entry === target): each serverless
  entry declares it; entry-node sniffs Cloud Run (K_SERVICE) / Docker
  (ZPAN_RUNTIME, set in the Dockerfile) / bare node. Cloudflare is detected from
  the D1 binding.
- Decouple the cloud payload: zpan-cloud-sdk fixes runtime { provider, target },
  so CloudInstanceInfo keeps that shape and buildCloudInstanceInfo maps to it;
  buildInstanceInfo serves the richer flat shape to the About API.
- Migrate PostHog instance telemetry to the runtime/platform shape and merge the
  duplicate runtimeInfo in licensing-admin into the shared one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 14:56:29 -04:00
saltboandClaude Opus 4.8 e700fd4977 feat(about): add changelog drawer, latest-version check, and commit hash
Maintain a CHANGELOG.md (Keep a Changelog format) at the repo root and surface
it on the admin About page:

- The page now shows the running build's short commit hash next to the version,
  linked to the GitHub commit. Commit is injected at build time via a new
  resolveAppCommit() (ZPAN_APP_COMMIT -> WORKERS_CI_COMMIT_SHA -> git rev-parse),
  wired through vite/tsup defines, the node entry, Docker, and CI.
- A new admin-only GET /api/system/changelog endpoint fetches CHANGELOG.md from
  master on GitHub, caches it, parses the latest released version, and reports
  whether an update is available (semver compare against the running version).
- The About page renders a "latest version" row with an update-available badge
  and a side drawer that displays the changelog markdown.

Tests cover the semver compare, changelog parse/fetch caching, the API wrapper,
and the route (admin-gated, parsed payload).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:38:29 -04:00
saltboandClaude Opus 4.8 9c16adbdc0 perf(quota): batch admin quota listing and move monthly reset to cron
The admin quota listing called getEffectiveQuota per org, firing ~8
sequential queries each (N+1). On D1 every query is a network round-trip,
so the endpoint scaled linearly with org count and risked the Workers
subrequest cap.

- Add getEffectiveQuotasByOrg: resolves every org in 2 queries (quota
  rows + active entitlements) and aggregates in memory. Route uses it.
- Remove the inline traffic-period reset writes from the read paths
  (getEffectiveQuota and the listing route). getEffectiveQuota already
  normalizes a stale period in memory, so reads stay correct.
- Add resetExpiredTrafficQuotas and run it on a new monthly cron
  (0 0 1 * *) for CF Workers and a daily idempotent interval for Node.
  The consume write path keeps its atomic reset-and-consume as a
  self-healing fallback if a scheduled run is missed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 03:08:27 -04:00
Jasper VanandClaude Opus 4.8 af43701daf fix(version): set app version global in node entry (#418)
* fix(version): resolve app version at runtime in node entry

E2E runs the Node server via tsx, which bypasses the tsup build-time
define, leaving __ZPAN_APP_VERSION__ unset so getAppVersion throws and
/api/licensing/pair returns 500. Resolve the version at runtime via
resolveAppVersion (git describe) when the global is unset; in the built
output the define inlines the constant, so the branch is never reached
and git is not invoked in production. Keeps the git-describe version as
the single source of truth across all paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(version): inject app version into docker build

The Docker build excludes .git from its context (.dockerignore), so the
build-time git describe in build:node would throw and break the image
build. Let resolveAppVersion read ZPAN_APP_VERSION, set it from an
APP_VERSION build arg in the builder stage, and pass the release tag from
the release workflow. git describe stays the default everywhere else, so
the version is unified across CF Workers, tsx, and Docker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:29:47 -04:00
saltbo 3b5f6c7fb5 fix(telemetry): persist detected site origin 2026-06-08 14:43:34 -04:00
saltbo 48b82cc804 fix(telemetry): avoid generic disable flag 2026-06-08 14:08:32 -04:00
saltbo 1a6a77435b fix(telemetry): disable reports during e2e 2026-06-08 14:04:57 -04:00
saltbo f52213b202 fix(telemetry): standardize instance reports 2026-06-08 13:47:20 -04:00
saltbo 400940204f fix(telemetry): align instance report fields 2026-06-08 13:25:55 -04:00
Jasper Van bd9f047ebb fix(telemetry): report instance after deployment (#417) 2026-06-08 12:40:59 -04:00
Jasper Van 1d7ac9b071 fix(telemetry): use built-in product reporting endpoint (#416)
* fix(telemetry): use built-in product reporting endpoint

* fix(telemetry): send reports through posthog capture
2026-06-08 11:32:15 -04:00
Jasper Van de0213ad5d feat: add PostHog instance telemetry (#414)
* feat: add PostHog instance telemetry

Agent-Profile: https://agent-kanban.dev/agents/7bf89fb1be06098c

* fix: include node os release in telemetry

Agent-Profile: https://agent-kanban.dev/agents/7bf89fb1be06098c
2026-06-08 10:50:54 -04:00
Jasper Van 9d70acfdea feat(licensing): support independent business authorization
Support independent Pro and Business licensing, migrate Cloud store integration through the SDK, gate Business-only credit billing features, and validate the Cloud store E2E flow.
2026-06-08 01:42:23 -04:00
saltbo ecb38df84d fix(downloads): make remote usage billing resilient 2026-06-07 12:47:07 -04:00
saltbo 88f6e22f72 fix(traffic): sync cloud usage in background 2026-05-09 11:46:26 -04:00
Jasper VanandBob 29102e623d feat: v2.6 Z6 — 6h background entitlement refresh (#345)
- Add server/services/licensing-refresh-runner.ts: shared runner with
  5-min dedup guard, structured INFO logs, and no-op for unbound state
- Add workers/scheduled.ts + export scheduled() in workers/bootstrap.ts
  for CF Workers cron (every 6 hours)
- Add [triggers] crons = ["0 */6 * * *"] to wrangler.toml
- Add setInterval refresh on boot in server/entry-node.ts with
  "licensing.refresh.scheduler.started interval=6h" log
- Add POST /api/licensing/refresh-cron?secret=... public endpoint
  (timing-safe secret comparison) for non-CF platforms
- Extract ZPAN_CLOUD_URL_DEFAULT to shared/constants.ts, replacing
  four duplicated literals
- Document REFRESH_CRON_SECRET + scheduler setup in all 5 non-CF
  deploy guides (vercel, netlify, aws-lambda, azure-functions, cloud-run)

Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f

Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
2026-04-24 08:22:10 -04:00
Jasper VanandBob 8005defd97 feat: v2.5.0 T1 — libSQL (Turso) platform adapter + Docker Turso opt-in (#326)
* feat: add libSQL (Turso) platform adapter and Docker opt-in

- server/platform/libsql.ts: createLibsqlPlatform() using @libsql/client +
  drizzle-orm/libsql; accepts plain env record; async migrate at boot;
  authToken optional for file:// URLs
- server/entry-node.ts: select platform at startup — libsql when
  TURSO_DATABASE_URL is set, otherwise existing SQLite via createNodePlatform()
- drizzle.config.ts: switch to turso dialect when TURSO_DATABASE_URL is set
- vitest.libsql.config.ts + server/platform/libsql.libsql-test.ts: smoke suite
  covering connect, migrations, insert/select against users + storages tables
- package.json: add @libsql/client dependency; add test:libsql script;
  externalize @libsql/client in build:node tsup command
- vitest.config.ts: exclude *.libsql-test.ts from coverage
- docs/deploy/docker.md: document Turso opt-in with copy-pasteable
  docker-compose snippet
- CONTRIBUTING.md: add Turso migrate path paragraph under Database Migrations

Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f

* refactor: turn bootstrap.ts into a Platform-accepting factory

- server/bootstrap.ts: replace singleton module-scope script with
  exportable createBootstrap(platform) async factory; reads
  BETTER_AUTH_SECRET/BETTER_AUTH_URL/TRUSTED_ORIGINS from platform.getEnv
  so every future entry (Lambda, Vercel, Netlify, Azure) can reuse it
- server/entry-node.ts: slim down to platform selection + createBootstrap
  call; no more duplicate auth/app wiring
- server/dev.ts: thin vite-dev-server entry that creates NodePlatform and
  calls createBootstrap; replaces the former default export in bootstrap.ts
- vite.config.ts: update node dev server entry to server/dev.ts
- server/platform/libsql.ts: fix getEnv to check env record before
  falling back to process.env, matching the cloudflare.ts pattern

Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f

* style: apply biome auto-fixes for pre-existing lint issues

Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f

---------

Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
2026-04-22 00:48:21 -04:00
saltboandClaude Opus 4.6 3b80eff413 feat: migrate from CF Pages to Workers with one-click deploy button
- Replace Pages Functions with Workers entry (`workers/bootstrap.ts`)
- Add Deploy to Cloudflare button in README
- Integrate `@cloudflare/vite-plugin` for CF dev with HMR
- Integrate `@hono/vite-dev-server` for Node dev with HMR
- `npm run dev` now defaults to CF Workers mode
- Add `run_worker_first = ["/api/*"]` so static assets stay free
- Extract shared Node bootstrap (`server/bootstrap.ts`) for reuse
- Update all docs from Pages to Workers references

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:40:15 -04:00
saltboandClaude Opus 4.6 ff1ad78eb5 fix(auth): fail-fast on missing secret, align trustedOrigins across entries
Both entries now throw when BETTER_AUTH_SECRET is absent instead of
silently using a hardcoded dev default. The CF Pages entry was missing
trustedOrigins entirely; it now reads TRUSTED_ORIGINS from env and
shares the same split/trim/filter logic as the Node entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:06:02 -04:00
saltboandClaude Opus 4.6 310e4950b3 refactor: flatten monorepo to single-package CF Pages Functions structure
Replace pnpm workspace monorepo (packages/server, packages/web, packages/shared)
with a flat single-package layout following Hono's pages-stack pattern. Switch from
pnpm to npm and from Workers+Assets to CF Pages Functions deployment model.

- Move source: packages/server/src/ → server/, packages/web/src/ → src/, packages/shared/src/ → shared/
- Add functions/api/[[route]].ts as CF Pages Functions entry (replaces entry-cloudflare.ts)
- Update 22 import paths: server uses relative, web uses @shared/@server aliases
- Merge three package.json into one, switch to npm
- Update wrangler.toml: remove main/assets (Pages auto-detects functions/ dir)
- Add per-directory tsconfig.json for VS Code type resolution
- Simplify Dockerfile for flat layout
- Fix react-pdf CSS import path (dist/esm/ → dist/)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 00:27:38 -04:00