Skip to content
CelPlume
select · Enteropen · Escclose Powered by Pagefind

Development Guide

Local setup, testing, coding conventions, and common pitfalls.

Install the following tools before you start.

ToolVersionPurpose
Node.js22 or laterJavaScript runtime for build tooling
bun1.3 or laterPackage manager for both workers/ and frontend/ (packageManager: bun@1.3.14)
wrangler4 or laterCloudflare Workers CLI for local API development

Install bun before you continue:

curl -fsSL https://bun.sh/install | bash

The project uses bun as the single package manager. Do not mix npm, pnpm, or yarn lockfiles into the repository.

Open two terminals. In the first, install the API dependencies; in the second, install the frontend dependencies.

cd workers && bun install
cd ../frontend && bun install

Copy the example file and edit the values that matter for local development:

cp workers/.dev.vars.example workers/.dev.vars

Set at least JWT_SECRET and ENCRYPTION_KEY to unique values. The file includes development defaults for the other settings, such as SMTP, Turnstile, and the initial administrator credentials.

Run the migrations against the local D1 database:

cd workers
bunx wrangler d1 migrations apply picumet-db --local

This command applies every pending migration in workers/migrations/ in order. Name new migration files NNNN_description.sql so the apply order stays deterministic.

Start the Workers API from the workers/ directory:

cd workers && bun run dev

The API listens on http://localhost:8787.

Start the frontend dev server from the frontend/ directory in the second terminal:

cd frontend && bun run dev

The frontend listens on http://localhost:5173. Vite proxies /api/* and /webdav/* requests to http://localhost:8787, so you do not need CORS configuration locally.

Open the following URLs:

  • Frontend: http://localhost:5173
  • API: http://localhost:8787

On first startup, workers/src/seed.ts creates the default administrator, a demo user, the default R2 provider with a root mount, and a demo folder. The seed runs once, guarded by the seed:done KV key.

The development seed accounts are:

RoleUsernamePassword
Administratoradminadmin123456
Demo userdemodemo123456

Override the development passwords with ADMIN_PASSWORD and DEMO_PASSWORD in .dev.vars. In production, supply a strong ADMIN_PASSWORD. Without one, the seed skips the administrator and the business API returns 503 until initialization completes.

Run these commands from each package root.

TaskCommandDirectory
Backend testsbun run testworkers/
Backend type checkbun run typecheckworkers/
Frontend testsbun run testfrontend/
Frontend coverage gatebun run test:coveragefrontend/
Frontend type checkbun run typecheckfrontend/
Frontend buildbun run buildfrontend/

The backend suite contains 127 test cases and runs against in-memory node:sqlite mocks for D1, KV, and R2 (see tests/helpers.ts), so it does not require workerd. The frontend suite contains 7 test cases plus a coverage gate that focuses on the security-critical modules src/lib/escape.ts and src/pages/Register.tsx (80% lines, 60% functions, 40% branches).

Continuous integration runs .github/workflows/ci.yml on push and pull requests to main. The workers job runs install, type check, and tests; the frontend job adds the coverage gate and a production build. CI never deploys; deploy with wrangler deploy manually.

Follow these conventions so the codebase stays consistent.

  • Files: kebab-case
  • React components: PascalCase
  • Functions and variables: camelCase
  • Constants: UPPER_SNAKE_CASE
  • Types and interfaces: PascalCase

Order imports as follows: external libraries first, then Cloudflare bindings, then project-internal modules, and type-only imports last.

  • Keep strict mode enabled.
  • Avoid any; if you must use it, add a comment that explains why.
  • Add an explicit return type to every function.
  • Run bun run typecheck in both packages after you change types.

Cover the following modules whenever you change them.

The permission algorithm in services/permissions/check.ts decides access with a priority order: administrator privilege, mount boundary, user root path, API-key permission scope, path rules, owner fallback, and default deny. Tests must lock in path segment boundaries: /users/alice must never match /users/alice2. Add cases for rule priority, wildcard patterns, and default deny.

Upload sessions transition through pending → uploading → verifying → completed, with failed, expired, and aborted terminal states. Multipart uploads add parts_uploaded and completing. Cover resume, abort, and the completion check that verifies part coverage and the final HEAD size.

Quota updates must be atomic. Test that concurrent uploads never exceed the configured limit and that delete and abort paths release reservations exactly once. Use the atomic UPDATE form instead of a read-modify-write sequence.

Re-run the security regression suite after any change to authentication, uploads, or storage: free-mode credential handling, WebDAV auth, SSRF checks, encryption, rate limiting fail-closed behavior, and one-time download token consumption.

Use Conventional Commits: type(scope): subject. Add the body with multiple -m flags, each flag one bullet point.

git commit -m "feat(upload): add multipart upload support for large files" \
-m "- Implement the multipart session API and the resume contract" \
-m "- Record part ETags server-side for completion verification"
git commit -m "fix(permission): fix a path boundary bypass in rule matching" \
-m "- Replace startsWith with isPathWithinBoundary" \
-m "- Add boundary tests for /users/alice and /users/alice2"

The examples above use English; commit messages in Chinese are equally welcome. Use these types and scopes.

TypeMeaning
featNew feature
fixBug fix
docsDocumentation only
styleFormatting, no behavior change
refactorCode change with no behavior change
perfPerformance improvement
testTest additions or changes
choreMaintenance
ScopeArea
authAuthentication and sessions
permissionPermission algorithm and rules
storageStorage providers
uploadUpload and multipart flows
downloadDownload gateway and tokens
uiFrontend components and pages
apiAPI routes and schemas
dbMigrations and repos

Review each change against this checklist before you push.

  • The feature works end to end through the actual UI or API.
  • Edge cases and error paths behave as documented.
  • TypeScript strict passes; no undocumented any.
  • Naming and import order follow the conventions above.
  • No dead code, leftover debug logging, or commented-out blocks.
  • New behavior has tests that would fail on a plausible regression.
  • bun run test and bun run typecheck pass in both packages.
  • Permission checks run on the canonical, normalized path.
  • Object storage keys and credentials never reach the client or logs.
  • Fail-closed paths stay closed: rate limits, free-mode, SSRF.
  • Database writes use atomic statements; no read-modify-write on quota.
  • Avoid unnecessary allocations or copies in hot request paths.
  • Update docs/API.md when you add or change an endpoint.
  • Update this guide and docs/ARCHITECTURE.md when conventions or structure change.

startsWith compares string prefixes and lets /users/alice match /users/alice2. The helper isPathWithinBoundary in utils/path.ts compares path segments instead.

Delete the database metadata inside a transaction first. Clean the object storage asynchronously afterwards. Record any cleanup failures in orphan_objects for reconciliation. Deleting the object first risks losing it when the metadata delete fails.

Do not read the quota, modify it, and write it back. Concurrency makes that sequence racy. Use a single UPDATE user_quotas SET used_storage = used_storage + ? ... statement.

Call normalizePath on every incoming path before permission checks, so that /users/../admin/secrets resolves to /admin/secrets and cannot bypass rules.

Hot reload is unreliable for the Workers API. After you change workers source, restart the process; to be safe, remove .wrangler and re-apply migrations to start from a clean state.

The codebase builds in dependency order. Each phase depends on the previous one, and each phase is complete when its acceptance criteria pass.

PhaseFocusDepends on
0Environment setup
1Authentication0
2Permission system1
3R2 object storage2
4Basic file management3
5Quota management4
6Move and rename5
7Password protection and shares6
8API keys and WebDAV7
9Advanced UI8
10Themes and i18n9
11Admin features10
12Security hardening11
13Multipart upload12
14Extra storage sources13
15Free mode14
16Testing and deployment15

Milestones along this order:

  • M1 (phase 4): a usable file management system
  • M2 (phase 8): complete API and sharing features
  • M3 (phase 11): multi-user production system
  • M4 (phase 15): full-featured release
  • M5 (phase 16): public release

Each phase ends when its acceptance criteria pass. Use this ordering as a guide for planning work on the remaining features.