summaryrefslogtreecommitdiffstats
path: root/CLAUDE.md
blob: 7c16e883acc80ac52649cd5045bab25117e4c66f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is a Mihon & Aniyomi extensions repository aggregator that:

- Fetches and mirrors extension repositories from multiple sources
- Builds a static website to browse and add extensions
- Uses R2 cache for fast extension data restoration
- Mirrors repository to GitLab for backup
- Automatically updates extensions every 4 hours via GitHub Actions

## Development Commands

```bash
# Install dependencies
bun install

# Start development server with hot reload
bun run dev

# Update extensions from upstream sources
bun run update

# Build the static site
bun run build

# Preview the production build
bun run preview

# Format code with Prettier
bun run format

# Check code formatting
bun run lint
```

## Architecture

### Core Build System

The build process uses Vite with SvelteKit and custom scripts:

1. **`scripts/update.ts`**: Main extension update and data generation script
    - Fetches extensions from upstream git repositories
    - Reads `extensions.json` configuration at project root
    - Checks remote git commit hashes for updates
    - Supports multiple modes:
        - `--generate-only`: Regenerate `data.json` without fetching updates or cache operations
        - `--quick`: Fast mode that only updates `extensions.json` with new hashes (used in CI)
        - `--no-cache`: Disable cache operations
        - Default mode: Full update with cache operations
    - Cache behavior:
        - Restores `static/` directory from S3 cache (only in full mode)
        - Uses manifest-based cache resolution with fallback prefixes
        - Clones repositories to `tmp/` and copies configured files to `static/`
        - Updates `extensions.json` with new commit hashes only after successful clone/copy
        - Uploads updated `static/` directory to S3 cache
        - Cleans up old cache entries (keeps most recent 10, max age 30 days)
    - Generates `static/data.json` with extension metadata
    - Sets `updated` output for CI/CD workflows (only when changes occur)

2. **Build order**: `update --generate-only → vite build`
    - First, `data.json` is regenerated from current extension state
    - Then Vite builds the SvelteKit app into `dist/`
    - Static assets from `static/` are included in the build

### Extension Configuration

Extensions are defined in `extensions.json` at the root with a nested structure by category:

```json
{
  "mihon": {
    "keiyoushi": {
      "name": "Keiyoushi",
      "source": "https://github.com/keiyoushi/extensions",
      "path": "/keiyoushi/index.min.json",
      "commit": "..."
    },
    "yuzono/manga": {
      "name": "Yuzono Manga",
      "source": "https://github.com/yuzono/manga-repo",
      "path": "/yuzono/manga/index.min.json",
      "commit": "..."
    }
  },
  "aniyomi": {
    "kohi-den": { ... }
  }
}
```

Each extension specifies:

- `source`: Git repository URL
- `path`: URL path for the extension index (can include subdirectories)
- `commit`: Current tracked commit hash
- The key (e.g., "keiyoushi", "yuzono/manga") is used as the directory name in `static/` and as part of the URL path

### Files Copied from Extensions

Defined in `scripts/config.ts` as `filesToCopy`:

- `index.json` - Full extension index
- `index.min.json` - Minified extension index
- `repo.json` - Repository metadata
- `apk/` - APK files directory
- `icon/` - Icon files directory

### Frontend

The frontend is a SvelteKit static site built with Vite:

- **SvelteKit**: Framework with static adapter (`@sveltejs/adapter-static`)
- **Svelte 5**: Modern reactive UI framework
- **TypeScript**: Type-safe component development
- **Meilisearch**: Advanced search with filtering and faceting
- **Prerendering**: All pages are prerendered at build time (`prerender = true`)

**Frontend Structure**:

- `src/routes/+layout.ts` - Loads `data.json` and provides it to all pages
- `src/routes/+page.svelte` - Home page with extension categories
- `src/routes/search/+page.svelte` - Search page with all extensions
- `src/lib/components/` - Reusable UI components:
    - `ExtensionCategory.svelte` - Category display with repos
    - `ExtensionCard.svelte` - Individual repo card
    - `ExtensionRow.svelte` - Table row for search results with NSFW badge
    - `MirrorSelector.svelte` - Domain selection dropdown
    - `Footer.svelte` - Page footer
- `src/lib/stores/` - Svelte stores for state management
- `src/lib/types.ts` - TypeScript type definitions
- `src/lib/search/` - Search functionality with Meilisearch integration:
    - `meilisearch.ts` - Meilisearch client and search operations
    - `debounce.ts` - Search debouncing utilities
    - `types.ts` - Search-specific type definitions
    - `utils.ts` - Search helper functions
- `src/app.css` - Global styles including NSFW badge styling
- `src/app.html` - HTML template

The frontend:

1. Fetches `data.json` on initial load via `+layout.ts`
2. Displays extension repositories grouped by category (mihon/aniyomi)
3. Provides mirror domain selection for extension URLs
4. Offers "Add Repo" links using `tachiyomi://` or `aniyomi://` protocols
5. Search page allows browsing all individual extensions from all repos with advanced filtering
6. Shows NSFW badge for extensions with adult content (when `nsfw: 1`)

### Extension Data Structure

Extensions fetched from upstream repositories contain:

- `name`: Display name (e.g., "Tachiyomi: MangaDex")
- `pkg`: Package identifier (e.g., "eu.kanade.tachiyomi.extension.en.mangadex")
- `version`: Version string
- `lang`: Language code (e.g., "en", "all")
- `apk`: APK filename
- `nsfw`: Integer flag (1 = NSFW content, 0 = safe)

### Caching System

The project uses S3-compatible storage (Cloudflare R2, Backblaze B2, AWS S3, etc.) for distributed caching of extension data with a sophisticated manifest-based system:

- **Cache Storage**: Compressed tar.zst files stored in S3 bucket
- **Compression**: Uses tar with zstd compression for fast compression/decompression
- **Manifest System**: JSON manifest tracks all cache entries with metadata for resolution
- **Distributed Locking**: Uses S3 metadata and conditional writes with instance IDs to coordinate updates
- **Cache Validation**: Validates cache integrity using file checksums before restoration
- **Automatic Cleanup**: Keeps most recent 10 caches, removes entries older than 30 days
- **Cache Scripts**: Located in `scripts/`
    - `cache.ts`: Main cache orchestration (restore and save operations with locking)
    - `cache/files.ts`: Tar archive creation/extraction with zstd compression and checksum validation
    - `cache/lock.ts`: Distributed lock implementation using S3 metadata and instance IDs
    - `cache/manifest.ts`: Cache manifest management for tracking and resolving cache entries
    - `cache/metadata.ts`: Cache metadata storage (checksums, timestamps) for validation
    - `cache/s3.ts`: S3 client wrapper, cache resolution, and cleanup logic
    - `cache/utils.ts`: Shared utilities and constants
    - `cache/logger.ts`: Logging utilities for transfers and progress

**S3 Configuration**:

Required environment variables in `.env`:

- `S3_ENDPOINT`: S3 endpoint URL
    - Cloudflare R2: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`
    - Backblaze B2: `https://s3.<REGION>.backblazeb2.com`
    - AWS S3: `https://s3.<REGION>.amazonaws.com`
- `S3_ACCESS_KEY_ID`: Access key ID
- `S3_SECRET_ACCESS_KEY`: Secret access key
- `S3_BUCKET_NAME`: Bucket name
- `S3_REGION`: Region (optional, use "auto" for R2)

**Cache Flow**:

**Restore**:

1. Resolve cache key using manifest (exact match or prefix fallback)
2. Validate local cache using checksums (skip download if valid)
3. Download tar.zst file from S3
4. Extract to `static/` directory
5. Update access timestamps in manifest

**Save**:

1. Acquire distributed lock using instance ID
2. Compress `static/` directory to tar.zst with checksums
3. Upload to S3 with streaming multipart upload
4. Save metadata (checksums, file list) to S3
5. Update manifest with new cache entry
6. Clean up old cache entries (keep 10 most recent, max age 30 days)
7. Release lock

### Deployment

The workflow is configured in `.github/workflows/update.yml`:

- **GitLab Mirror**: Syncs entire repository to GitLab using SSH for backup and redundancy

The workflow runs on:

- Schedule: Every 4 hours (`0 */4 * * *`)
- Manual: `workflow_dispatch`
- Push to main branch (excluding bot commits)

The workflow has two jobs:

- `update`: Updates extensions using quick mode, commits changes to `extensions.json`
- `sync-to-gitlab`: Mirrors repository to GitLab (runs after update when changes occur)

Deployments and mirroring run when:

- Extensions have updates (`updated=true` from `update.ts`)
- Workflow is manually triggered (`workflow_dispatch`)

## Important Patterns

### Update Logic

The update script (`scripts/update.ts`) uses a dual-config sync system:

- `extensions.json` (root): The desired/target state
- `static/data.json`: Contains the successfully synced state (what's actually downloaded)

Update flow:

1. Compare remote hash with synced hash (from `data.json`)
2. If different, or if files are missing in `static/`, queue for update
3. Clone and copy files for each extension
4. **Only after successful clone/copy**, update `extensions.json` with new commit hash
5. Generate new `data.json` with updated commit info
6. Failed clones don't update hashes, ensuring retry on next run

CI behavior:

- In CI: only downloads if there are actual hash changes
- Locally (non-CI): always downloads to restore missing files
- Manual workflow triggers: force downloads regardless of hash changes
- Sets `updated` output for CI/CD workflows based on successful updates only

### CI Skip Pattern

Commits from the workflow use `[skip ci]` to prevent recursive builds:

```bash
git commit -m "chore: update extensions.json"
```

### Code Standards

- **Line Endings**: LF (Line Feed) is enforced project-wide via:
    - `.gitattributes`: `* text=auto eol=lf`
    - `.prettierrc`: `"endOfLine": "lf"`
    - This ensures cross-platform consistency (Windows/Linux/CI)

- **Formatting**: Prettier with:
    - 4 spaces for indentation
    - Single quotes
    - No trailing commas
    - 100 character line width

### Configuration

All deployable domains are listed in `scripts/config.ts` under `config.domains`. The frontend allows users to select which mirror to use for extension URLs.