|
1 | 1 | # CLAUDE.md |
2 | 2 |
|
| 3 | +## Project Overview |
| 4 | + |
| 5 | +R2-Explorer is a Google Drive-like interface for Cloudflare R2 storage buckets. It is a **pnpm monorepo** containing four packages, published as a single npm package (`r2-explorer`) that users deploy via Cloudflare Workers. |
| 6 | + |
| 7 | +**Stack:** TypeScript + Hono (backend) · Vue 3 + Quasar (frontend) · Cloudflare Workers (runtime) · Vitest + Playwright (tests) · Biome (linting) |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Repository Structure |
| 12 | + |
| 13 | +``` |
| 14 | +R2-Explorer/ |
| 15 | +├── packages/ |
| 16 | +│ ├── worker/ # Backend API — published npm package (r2-explorer) |
| 17 | +│ ├── dashboard/ # Frontend SPA — bundled into worker at build time |
| 18 | +│ ├── docs/ # Documentation site (VitePress) |
| 19 | +│ └── github-action/ # GitHub Actions deployment helper |
| 20 | +├── template/ # Starter template for end users |
| 21 | +├── biome.json # Linting/formatting config (replaces ESLint + Prettier) |
| 22 | +├── pnpm-workspace.yaml # Monorepo workspace definition |
| 23 | +└── package.json # Root scripts |
| 24 | +``` |
| 25 | + |
| 26 | +--- |
| 27 | + |
3 | 28 | ## Git Workflow |
4 | 29 |
|
5 | | -- **Never commit directly to `main`.** Always create a feature branch for changes. |
| 30 | +- **Never commit directly to `main`.** Always create a feature branch. |
6 | 31 | - **Run `pnpm lint` before every commit.** Fix any lint errors before committing. |
| 32 | +- Branch naming: `feat/description`, `fix/description`, `chore/description` |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +## Commands Reference |
| 37 | + |
| 38 | +```bash |
| 39 | +# Linting |
| 40 | +pnpm lint # Biome check + auto-fix |
| 41 | + |
| 42 | +# Building |
| 43 | +pnpm build # Build dashboard then worker (full) |
| 44 | +pnpm build-dashboard # Build Vue SPA to packages/dashboard/dist/spa/ |
| 45 | +pnpm build-worker # Compile TS + bundle dashboard into worker |
| 46 | + |
| 47 | +# Testing |
| 48 | +pnpm test # Run worker + dashboard tests |
| 49 | +pnpm test:e2e # Playwright E2E tests |
| 50 | +pnpm --filter r2-explorer-dashboard test # Dashboard component tests only |
| 51 | + |
| 52 | +# Development |
| 53 | +cd packages/dashboard && pnpm dev # Dashboard dev server |
| 54 | +cd packages/worker && pnpm test # Worker integration tests |
| 55 | + |
| 56 | +# Release |
| 57 | +pnpm changeset # Create changeset for PR |
| 58 | +pnpm changeset --empty # Empty changeset for internal-only PRs |
| 59 | +pnpm release # Build + publish to npm (CI only) |
| 60 | +``` |
| 61 | + |
| 62 | +--- |
7 | 63 |
|
8 | 64 | ## Changesets |
9 | 65 |
|
10 | | -Every PR must include a changeset. Run `pnpm changeset` to create one. |
| 66 | +Every PR must include a changeset: |
11 | 67 |
|
12 | | -- If the PR includes user-facing changes, write a changelog entry describing what changed with examples. |
13 | | -- If the PR is internal-only (refactoring, CI, docs, tooling), add an empty changeset with `pnpm changeset --empty`. |
| 68 | +- **User-facing changes** (new features, bug fixes, API changes): `pnpm changeset` — write a meaningful changelog entry with examples. |
| 69 | +- **Internal-only changes** (refactoring, CI, docs, tooling): `pnpm changeset --empty`. |
| 70 | + |
| 71 | +--- |
14 | 72 |
|
15 | 73 | ## Testing |
16 | 74 |
|
17 | | -- **Any UI changes must be covered by E2E tests.** Playwright E2E tests live in `packages/dashboard/e2e/`. Run them with `pnpm test:e2e`. |
18 | | -- Run component tests with `pnpm --filter r2-explorer-dashboard test`. |
19 | | -- Run all tests (worker + dashboard) with `pnpm test`. |
| 75 | +- **UI changes must be covered by E2E tests.** Playwright tests live in `packages/dashboard/e2e/`. Run with `pnpm test:e2e`. |
| 76 | +- Worker integration tests live in `packages/worker/tests/`. Run with `cd packages/worker && pnpm test`. |
| 77 | +- Dashboard component tests: `pnpm --filter r2-explorer-dashboard test`. |
| 78 | +- All tests: `pnpm test`. |
| 79 | + |
| 80 | +--- |
| 81 | + |
| 82 | +## Package Details |
| 83 | + |
| 84 | +### Worker (`packages/worker/`) |
| 85 | + |
| 86 | +**Entry point:** `src/index.ts` |
| 87 | +**Framework:** Hono + Chanfana (OpenAPI/Zod) |
| 88 | +**Main export:** `R2Explorer(config?: R2ExplorerConfig)` |
| 89 | + |
| 90 | +**API Routes:** |
| 91 | +| Route | Purpose | |
| 92 | +|-------|---------| |
| 93 | +| `GET /api/server/config` | Server configuration | |
| 94 | +| `GET /api/buckets/:bucket` | List objects | |
| 95 | +| `GET /api/buckets/:bucket/:key` | Get object | |
| 96 | +| `HEAD /api/buckets/:bucket/:key` | Head object | |
| 97 | +| `POST /api/buckets/:bucket/upload` | Upload file | |
| 98 | +| `POST /api/buckets/:bucket/:key` | Update metadata | |
| 99 | +| `POST /api/buckets/:bucket/delete` | Delete objects | |
| 100 | +| `POST /api/buckets/:bucket/move` | Move/rename object | |
| 101 | +| `POST /api/buckets/:bucket/copy` | Copy object | |
| 102 | +| `POST /api/buckets/:bucket/folder` | Create folder | |
| 103 | +| `POST /api/buckets/:bucket/:key/share` | Create share link | |
| 104 | +| `GET /api/buckets/:bucket/shares` | List share links | |
| 105 | +| `DELETE /api/buckets/:bucket/share/:shareId` | Delete share link | |
| 106 | +| `GET /share/:shareId` | Public file access (no auth) | |
| 107 | +| `POST /api/buckets/:bucket/multipart/*` | Multipart upload | |
| 108 | +| `POST /api/emails/send` | Send email | |
| 109 | + |
| 110 | +**Middleware chain:** CORS → ReadOnly → Authentication → Routes |
| 111 | + |
| 112 | +**Authentication:** Basic Auth (`basicAuth` middleware) or Cloudflare Access (`@hono/cloudflare-access`). Both configured via `R2ExplorerConfig`. |
| 113 | + |
| 114 | +**Read-only mode:** Default `true`. Blocks write operations. Disable via `R2Explorer({ readonly: false })`. |
| 115 | + |
| 116 | +### Dashboard (`packages/dashboard/`) |
| 117 | + |
| 118 | +Vue 3 SPA built with Quasar. Compiled to `dist/spa/`, then copied into `packages/worker/dashboard/` during `pnpm build-worker`. Served via Cloudflare Workers Assets binding. |
| 119 | + |
| 120 | +**Key directories:** |
| 121 | +``` |
| 122 | +src/ |
| 123 | +├── components/ # Reusable Vue components |
| 124 | +├── pages/ # Route-level pages |
| 125 | +├── layouts/ # Page layouts (MainLayout, AuthLayout) |
| 126 | +├── stores/ # Pinia state (main-store.js, auth-store.js) |
| 127 | +├── router/ # Vue Router config |
| 128 | +├── boot/ # Quasar boot files (axios, auth, bus) |
| 129 | +└── appUtils.js # API client (all fetch calls go here) |
| 130 | +``` |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +## Adding a New API Endpoint |
| 135 | + |
| 136 | +1. Create a file in `packages/worker/src/modules/<feature>/myEndpoint.ts`. |
| 137 | +2. Extend `OpenAPIRoute` from Chanfana and define a Zod schema. |
| 138 | +3. Implement the `handle(c)` method. |
| 139 | +4. Register the route in `packages/worker/src/index.ts`. |
| 140 | +5. Add integration tests in `packages/worker/tests/integration/`. |
| 141 | +6. If the endpoint has a UI, update `packages/dashboard/src/appUtils.js`. |
| 142 | + |
| 143 | +```typescript |
| 144 | +import { OpenAPIRoute } from "chanfana"; |
| 145 | +import { z } from "zod"; |
| 146 | + |
| 147 | +export class MyEndpoint extends OpenAPIRoute { |
| 148 | + schema = { |
| 149 | + request: { |
| 150 | + params: z.object({ bucket: z.string() }), |
| 151 | + }, |
| 152 | + responses: { "200": { description: "Success" } }, |
| 153 | + }; |
| 154 | + |
| 155 | + async handle(c) { |
| 156 | + const { params } = await this.getValidatedData<typeof this.schema>(); |
| 157 | + return c.json({ success: true }); |
| 158 | + } |
| 159 | +} |
| 160 | +``` |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Code Style |
| 165 | + |
| 166 | +- **Linter/Formatter:** Biome (`biome.json`). Run `pnpm lint` to check and auto-fix. |
| 167 | +- **TypeScript:** Strict mode. Types in `packages/worker/src/types.d.ts`. |
| 168 | +- **Vue:** Composition API. Quasar components for UI consistency. |
| 169 | +- **Imports:** Managed by Biome (auto-sorted on lint). |
| 170 | +- **Quotes:** Double quotes in JS/TS. |
| 171 | +- **Indentation:** Tabs (code), 2 spaces (YAML). |
| 172 | + |
| 173 | +--- |
| 174 | + |
| 175 | +## Important Files |
| 176 | + |
| 177 | +| File | Purpose | |
| 178 | +|------|---------| |
| 179 | +| `packages/worker/src/index.ts` | Worker entry — route registration, auth middleware | |
| 180 | +| `packages/worker/src/types.d.ts` | TypeScript types (Config, Context, ShareMetadata) | |
| 181 | +| `packages/worker/src/foundation/settings.ts` | Package version | |
| 182 | +| `packages/dashboard/src/appUtils.js` | All API fetch calls from the dashboard | |
| 183 | +| `packages/dashboard/src/pages/files/FilesFolderPage.vue` | Main file browser UI | |
| 184 | +| `packages/dashboard/src/components/files/ShareFile.vue` | Share link UI | |
| 185 | +| `packages/dashboard/src/components/utils/DragAndDrop.vue` | Upload drag-and-drop | |
| 186 | +| `template/src/index.ts` | End-user entry point example | |
| 187 | +| `template/wrangler.toml` | End-user Wrangler config example | |
| 188 | +| `biome.json` | Linting/formatting rules | |
| 189 | +| `pnpm-workspace.yaml` | Workspace package definitions | |
| 190 | + |
| 191 | +--- |
| 192 | + |
| 193 | +## Share Links |
| 194 | + |
| 195 | +Share metadata is stored in R2 under the `.r2-explorer/sharable-links/` prefix. Passwords are hashed with SHA-256. The public `/share/:shareId` endpoint bypasses authentication and checks expiration on every request. |
| 196 | + |
| 197 | +--- |
| 198 | + |
| 199 | +## Build Pipeline |
| 200 | + |
| 201 | +1. `pnpm build-dashboard` → Quasar compiles Vue SPA to `packages/dashboard/dist/spa/` |
| 202 | +2. `pnpm build-worker` → tsup compiles TS, copies dashboard dist to `packages/worker/dashboard/`, copies README + LICENSE |
| 203 | +3. `pnpm package` → Creates npm tarball from `packages/worker/` |
| 204 | +4. `pnpm release` (CI only) → Publishes to npm via Changesets |
| 205 | + |
| 206 | +--- |
| 207 | + |
| 208 | +## Resources |
| 209 | + |
| 210 | +- Docs: https://r2explorer.com |
| 211 | +- Demo: https://demo.r2explorer.com |
| 212 | +- Hono: https://hono.dev |
| 213 | +- Chanfana (OpenAPI): https://chanfana.pages.dev |
| 214 | +- Quasar: https://quasar.dev |
| 215 | +- Cloudflare R2: https://developers.cloudflare.com/r2/ |
0 commit comments