feat: initial services infrastructure & hello world api (#1)

This commit is contained in:
Zack Pollard
2025-09-10 14:35:21 +01:00
committed by GitHub
parent 45ca315087
commit a7f273eacc
35 changed files with 4537 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
name: Build
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
ENVIRONMENT: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}
TG_NON_INTERACTIVE: 'true'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: '.nvmrc'
- name: Run pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Upload build output
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: build-output
path: 'dist'
retention-days: 7
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: [build]
env:
TF_VAR_stage: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }}
TF_VAR_dist_dir: ${{github.workspace}}/dist
OP_SERVICE_ACCOUNT_TOKEN: ${{ github.ref == 'refs/heads/main' && secrets.OP_TF_PROD_ENV || secrets.OP_TF_DEV_ENV }}
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Get build artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: 'build-output'
path: '${{ github.workspace }}/dist'
- name: Install 1Password CLI
uses: 1password/install-cli-action@707af61a6c8b4fa7f9476328c035208fba6b54d7 # v2
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@697a75e2c3186d3c037c2c159855cf2d566542ba # use-mise-action-0.0.1
- name: Deploy All
working-directory: ${{ github.workspace }}/deployment
run: mise run tf:apply
# - name: Deploy Output
# id: deploy-output
# working-directory: ${{ github.workspace }}/deployment/modules/cloudflare/static-pages
# run: |
# echo "output=$(op run --no-masking --env-file='../../../.env' -- terragrunt output -json | jq -c .)" >> $GITHUB_OUTPUT
#
# - name: Comment
# uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3
# if: ${{ github.event_name == 'pull_request' }}
# with:
# number: ${{ github.event.number }}
# body: |
# 📖 Preview of ${{ matrix.app_name }}.immich.app deployed to [${{ fromJson(steps.deploy-output.outputs.output).immich_subdomain.value }}](https://${{ fromJson(steps.deploy-output.outputs.output).immich_subdomain.value }})
# emojis: 'rocket'
# body-include: '<!-- ${{ matrix.app_name }} PR URL -->'
+36
View File
@@ -0,0 +1,36 @@
name: Docs destroy
on:
pull_request_target:
types: [closed]
env:
TF_VAR_dist_dir: ${{ github.workspace }}/dist
TF_VAR_stage: pr-${{ github.event.number }}
OP_SERVICE_ACCOUNT_TOKEN: ${{ matrix.environment == 'prod' && secrets.OP_TF_PROD_ENV || secrets.OP_TF_DEV_ENV }}
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
environment: ['dev', 'prod']
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Install 1Password CLI
uses: 1password/install-cli-action@707af61a6c8b4fa7f9476328c035208fba6b54d7 # v2
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@697a75e2c3186d3c037c2c159855cf2d566542ba # use-mise-action-0.0.1
- name: Destroy All
working-directory: ${{ github.workspace }}/deployment
run: mise run tf:destroy
# - name: Comment
# uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3
# with:
# number: ${{ github.event.number }}
# delete: true
# body-include: '<!-- Docs PR URL -->'
+43
View File
@@ -0,0 +1,43 @@
name: Test
on:
workflow_dispatch:
pull_request:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: '.nvmrc'
- name: Run pnpm install
run: pnpm install --frozen-lockfile
- name: Run linter
run: pnpm run lint
if: ${{ !cancelled() }}
- name: Run formatter
run: pnpm run format
if: ${{ !cancelled() }}
- name: Run tsc
run: pnpm run check
if: ${{ !cancelled() }}
- name: Run unit tests
run: pnpm run test
if: ${{ !cancelled() }}
+41
View File
@@ -0,0 +1,41 @@
# Dependencies
node_modules/
.pnp
.pnp.js
# Testing
coverage/
# Production
dist/
build/
# Wrangler
.wrangler/
.dev.vars
# Logs
logs/
*.log
npm-debug.log*
# Editor
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Terraform
*.tfstate
*.tfstate.*
.terraform/
.terragrunt-cache/
*.tfplan
*.tfvars
!*.tfvars.example
# Misc
.cache/
tmp/
+44
View File
@@ -0,0 +1,44 @@
[tools]
opentofu = "1.10.3"
terragrunt = "0.87.0"
[env]
TF_VAR_dist_dir="{{config_root}}/dist"
[tasks.tg]
run = "op run '--env-file={{config_root}}/deployment/.env' -- terragrunt"
description = "Wrapper for terragrunt"
dir = "{{cwd}}"
[tasks."tg:fmt"]
run = "mise run tg hclfmt"
description = "Format terragrunt files"
[tasks.tf]
run = "mise run tg run --all"
description = "Wrapper for terragrunt run-all"
dir = "{{cwd}}"
[tasks."tf:fmt"]
run = "tofu fmt -recursive tf/"
description = "Format terraform files"
[tasks."tf:init"]
run = "mise run tf init -- -reconfigure"
dir = "{{cwd}}"
[tasks."tf:plan"]
run = "mise run tf plan"
dir = "{{cwd}}"
[tasks."tf:apply"]
run = "mise run tf apply"
dir = "{{cwd}}"
[tasks."tf:destroy"]
run = "mise run tf destroy"
dir = "{{cwd}}"
[settings]
experimental = true
idiomatic_version_file_enable_tools = []
+1
View File
@@ -0,0 +1 @@
22.19.0
+10
View File
@@ -0,0 +1,10 @@
node_modules/
dist/
build/
.wrangler/
.terragrunt-cache/
.terraform/
*.tfstate
*.tfstate.*
coverage/
deployment/
+8
View File
@@ -0,0 +1,8 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"semi": true,
"organizeImportsSkipDestructiveCodeActions": true,
"plugins": ["prettier-plugin-organize-imports"]
}
+180
View File
@@ -0,0 +1,180 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is the Immich Workers repository - a monorepo for Cloudflare Workers that provide backend/API services. These workers are deployed independently from the frontend applications.
## Common Commands
### Root Level
```bash
pnpm install # Install all dependencies
pnpm run lint # Lint all workers
pnpm run format # Check formatting
pnpm run test # Run all tests
pnpm run typecheck # Type-check all workers
```
### Worker Development
```bash
cd apps/<worker-name>
pnpm install # Install worker dependencies
pnpm run dev # Start development server
pnpm run build # Build for production
pnpm run deploy # Deploy directly to Cloudflare
pnpm run test # Run worker tests
```
## Architecture
### Repository Structure
```
apps/
├── hello/ # Example hello world worker
├── datasets/ # Datasets API worker
└── .../ # Other worker applications
deployment/
├── modules/ # Terraform modules
│ └── cloudflare/
│ └── workers/
│ └── generic/ # Reusable worker module
└── terragrunt/ # Terragrunt configurations
├── dev/ # Development environment
├── staging/ # Staging environment
└── prod/ # Production environment
src/
└── lib/ # Shared libraries and utilities
```
### Worker Structure
Each worker in `apps/<worker-name>/` contains:
- `src/index.ts` - Main worker entry point
- `wrangler.toml` - Cloudflare Worker configuration
- `package.json` - Dependencies and scripts
- `tsconfig.json` - TypeScript configuration
- `vitest.config.ts` - Test configuration
- `worker-configuration.d.ts` - Environment type definitions
### Technology Stack
- **Runtime**: Cloudflare Workers
- **Language**: TypeScript
- **Build Tool**: Wrangler CLI
- **Testing**: Vitest with Miniflare
- **Infrastructure**: Terraform/Terragrunt
- **Package Manager**: pnpm with workspaces
## Deployment
### Direct Deployment (Wrangler)
```bash
cd apps/<worker-name>
pnpm run deploy # Deploy to default environment
pnpm run deploy:staging # Deploy to staging
pnpm run deploy:production # Deploy to production
```
### Infrastructure as Code (Terraform/Terragrunt)
```bash
# Set up required environment variables
export TF_VAR_tf_state_postgres_conn_str="postgresql://user:pass@host/dbname"
export TF_VAR_env="dev" # Environment (dev/staging/prod)
export TF_VAR_stage="dev" # Stage
export TF_VAR_app_name="hello" # App name
export TF_VAR_cloudflare_account_id="your-account-id"
# Deploy with Terragrunt
cd deployment/modules/cloudflare/workers/<worker-name>
terragrunt init
terragrunt plan
terragrunt apply
```
The deployment uses the same Terragrunt pattern as other Immich infrastructure:
- PostgreSQL backend for state storage
- Remote state references for API keys and account info
- Schema naming: `cloudflare_workers_immich_app_${app_name}_${env}${stage}`
## Environment Configuration
### Local Development
Create `.dev.vars` in the worker directory:
```
SECRET_KEY=local_secret
API_ENDPOINT=https://api.example.com
```
### Production Secrets
Use Wrangler or Terraform to set production secrets:
```bash
wrangler secret put SECRET_KEY
```
## Creating a New Worker
1. Create worker directory: `apps/<worker-name>/`
2. Copy structure from `apps/hello/` as template
3. Update `wrangler.toml` with worker name
4. Implement worker logic in `src/index.ts`
5. Add Terragrunt config if using IaC deployment
## Testing
Workers use Vitest with Miniflare for testing:
```bash
cd apps/<worker-name>
pnpm run test # Run tests once
pnpm run test:watch # Run tests in watch mode
```
## Common Patterns
### Request Handling
```typescript
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// Handle request
},
};
```
### CORS Headers
```typescript
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
```
### Error Handling
```typescript
try {
// Worker logic
} catch (error) {
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
```
+83
View File
@@ -0,0 +1,83 @@
# Immich Workers
Monorepo for Immich Cloudflare Workers (backend-only/API services).
## Structure
```
apps/
├── hello/ # Hello world example worker
├── datasets/ # Datasets API worker
└── .../ # Other worker applications
deployment/
├── modules/ # Terraform modules
└── terragrunt/ # Terragrunt configurations
src/
└── lib/ # Shared libraries and utilities
```
## Development
### Setup
```bash
npm install
```
### Working with Individual Workers
```bash
cd apps/<worker-name>
npm install
npm run dev # Start development server
npm run test # Run tests
npm run build # Build for production
npm run deploy # Deploy to Cloudflare
```
### Global Commands
```bash
npm run lint # Lint all workers
npm run format # Check formatting
npm run test # Run all tests
npm run typecheck # Type-check all workers
```
## Deployment
### Using Wrangler (Direct)
```bash
cd apps/<worker-name>
npm run deploy
```
### Using Terraform/Terragrunt
```bash
cd deployment/terragrunt/<environment>/<worker-name>
terragrunt plan
terragrunt apply
```
## Creating a New Worker
1. Create directory: `apps/<worker-name>/`
2. Add `package.json`, `wrangler.toml`, `tsconfig.json`
3. Create source in `src/index.ts`
4. Add terraform module in `deployment/modules/cloudflare/workers/<worker-name>/`
5. Add terragrunt config in `deployment/terragrunt/`
## Environment Variables
Workers use `.dev.vars` for local development and Cloudflare secrets for production.
Example `.dev.vars`:
```
MY_SECRET=secret_value
API_KEY=test_key
```
+6
View File
@@ -0,0 +1,6 @@
node_modules/
dist/
.wrangler/
.dev.vars
.env
.env.local
+25
View File
@@ -0,0 +1,25 @@
# hello.immich.app
A simple Cloudflare Worker API example for the Immich project.
## Endpoints
- `GET /` - Returns a hello message
- `GET /health` - Health check endpoint
- `GET /api/greet?name=<name>` - Personalized greeting
## Development
```bash
# Install dependencies
pnpm install
# Run locally
pnpm run dev
```
## Testing
```bash
pnpm test
```
+13
View File
@@ -0,0 +1,13 @@
{
"name": "@immich-services/hello",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "wrangler dev",
"build": "wrangler deploy --dry-run --outdir ../../dist/hello",
"tail": "wrangler tail",
"test": "vitest",
"check": "tsc --noEmit"
}
}
+37
View File
@@ -0,0 +1,37 @@
import { SELF } from 'cloudflare:test';
import { describe, expect, it } from 'vitest';
describe('Hello Worker', () => {
it('should return hello message on root path', async () => {
const response = await SELF.fetch('https://example.com/');
const data = (await response.json()) as any;
expect(response.status).toBe(200);
expect(data).toHaveProperty('message', 'Hello from Immich Worker API!');
expect(data).toHaveProperty('timestamp');
});
it('should return health status', async () => {
const response = await SELF.fetch('https://example.com/health');
const data = (await response.json()) as any;
expect(response.status).toBe(200);
expect(data).toHaveProperty('status', 'healthy');
});
it('should greet with name parameter', async () => {
const response = await SELF.fetch('https://example.com/api/greet?name=Claude');
const data = (await response.json()) as any;
expect(response.status).toBe(200);
expect(data).toHaveProperty('greeting', 'Hello, Claude!');
});
it('should return 404 for unknown routes', async () => {
const response = await SELF.fetch('https://example.com/unknown');
const data = (await response.json()) as any;
expect(response.status).toBe(404);
expect(data).toHaveProperty('error', 'Not Found');
});
});
+72
View File
@@ -0,0 +1,72 @@
export default {
// eslint-disable-next-line @typescript-eslint/require-await
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
// Handle different routes
switch (url.pathname) {
case '/': {
return new Response(
JSON.stringify({
message: 'Hello from Immich Worker API!',
timestamp: new Date().toISOString(),
path: url.pathname,
}),
{
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
},
);
}
case '/health': {
return new Response(
JSON.stringify({
status: 'healthy',
timestamp: new Date().toISOString(),
}),
{
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
},
);
}
case '/api/greet': {
const name = url.searchParams.get('name') || 'World';
return new Response(
JSON.stringify({
greeting: `Hello, ${name}!`,
timestamp: new Date().toISOString(),
}),
{
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
},
);
}
default: {
return new Response(
JSON.stringify({
error: 'Not Found',
path: url.pathname,
}),
{
status: 404,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
},
);
}
}
},
};
+3
View File
@@ -0,0 +1,3 @@
import baseConfig from '../../vitest.base.config.js';
export default baseConfig;
+7
View File
@@ -0,0 +1,7 @@
interface Env {
// Add environment variables here as needed
// Example:
// MY_KV_NAMESPACE: KVNamespace;
// MY_DURABLE_OBJECT: DurableObjectNamespace;
MY_SECRET: string;
}
+13
View File
@@ -0,0 +1,13 @@
name = "hello-immich-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"
# Uncomment to add KV namespaces, Durable Objects, etc.
# [[kv_namespaces]]
# binding = "MY_KV_NAMESPACE"
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# [[durable_objects.bindings]]
# name = "MY_DURABLE_OBJECT"
# class_name = "MyDurableObject"
# script_name = "my-durable-object"
+4
View File
@@ -0,0 +1,4 @@
export TF_VAR_cloudflare_account_id="op://tf/cloudflare/account_id"
export TF_VAR_cloudflare_api_token="op://tf/cloudflare/api_token"
export TF_VAR_tf_state_postgres_conn_str="op://tf/tf_state/postgres_conn_str"
export TF_VAR_env=$ENVIRONMENT
@@ -0,0 +1,19 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "5.9.0"
constraints = "~> 5.0"
hashes = [
"h1:yNvFe2InCb4foqjWd+Sz9DIX6mdk30iRycsmm/fZarY=",
"zh:3ae6f70f4e2961e84b89b337d268db0537c6dd0e66d4ccf32cbacc950f7a2807",
"zh:4472d1d1629c3c3a6a23672691ed0852bea9fcd9e39a213d388616f93adaaeb0",
"zh:4680cb586233b4702abb9fc69615bca6ebe9547ddaceaa0d0439bccc7c773905",
"zh:51fd157b544644438ab827e288c8173ecbf31cd92b3b75a8446569db35c00cab",
"zh:66aaeb1cb991b982f81564ea52a18ba962b43e86d9e5c76ac591fa522a4a0db6",
"zh:d271308040efe8324633810d8c58142310da5f88eb223422fd13daa73e944316",
"zh:e56c66588135878081ffa8c2aa5da969d56ff96d4ecb4b0ab6b8b496830cf7c2",
"zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32",
"zh:fde7a7d2bda2784e78c0bcc39155e0b09c31dd4a4fa65c26e0e8fe858445ecfc",
]
}
@@ -0,0 +1,11 @@
terraform {
backend "pg" {}
required_version = "~> 1.7"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 5"
}
}
}
@@ -0,0 +1,5 @@
locals {
resource_stage = var.stage != "" ? "-${var.stage}" : ""
resource_env = "-${var.env}"
resource_suffix = "${local.resource_env}${local.resource_stage}"
}
@@ -0,0 +1,3 @@
provider "cloudflare" {
api_token = data.terraform_remote_state.api_keys_state.outputs.terraform_key_cloudflare_account
}
@@ -0,0 +1,17 @@
data "terraform_remote_state" "api_keys_state" {
backend = "pg"
config = {
conn_str = var.tf_state_postgres_conn_str
schema_name = "prod_cloudflare_api_keys"
}
}
data "terraform_remote_state" "cloudflare_account" {
backend = "pg"
config = {
conn_str = var.tf_state_postgres_conn_str
schema_name = "prod_cloudflare_account"
}
}
@@ -0,0 +1,30 @@
terraform {
source = "."
extra_arguments custom_vars {
commands = get_terraform_commands_that_need_vars()
}
}
include {
path = find_in_parent_folders("state.hcl")
}
locals {
env = get_env("TF_VAR_env")
stage = get_env("TF_VAR_stage")
app_name = "hello"
}
inputs = {
app_name = local.app_name
}
remote_state {
backend = "pg"
config = {
conn_str = get_env("TF_VAR_tf_state_postgres_conn_str")
schema_name = "services_cloudflare_workers_${local.app_name}_immich_app_${local.env}${local.stage}"
}
}
@@ -0,0 +1,6 @@
variable "tf_state_postgres_conn_str" {}
variable "stage" {}
variable "env" {}
variable "app_name" {}
variable "cloudflare_account_id" {}
variable "dist_dir" {}
@@ -0,0 +1,63 @@
resource "cloudflare_worker" "worker" {
account_id = var.cloudflare_account_id
name = "${var.app_name}-api${local.resource_suffix}"
logpush = true
}
resource "cloudflare_worker_version" "worker" {
account_id = var.cloudflare_account_id
worker_id = cloudflare_worker.worker.id
bindings = [
{
name = "EXAMPLE_BINDING"
type = "plain_text"
text = "example_value"
}
]
compatibility_date = "2025-09-09"
compatibility_flags = ["nodejs_compat"]
main_module = "index.js"
modules = [
{
content_file = "${var.dist_dir}/${var.app_name}/index.js"
content_type = "application/javascript+module"
name = "index.js"
}
]
}
resource "cloudflare_workers_deployment" "worker" {
account_id = var.cloudflare_account_id
script_name = cloudflare_worker.worker.name
strategy = "percentage"
versions = [
{
percentage = 100
version_id = cloudflare_worker_version.worker.id
}
]
}
data "cloudflare_zone" "immich_app" {
filter = {
name = "immich.cloud"
}
}
resource "cloudflare_workers_custom_domain" "worker" {
account_id = var.cloudflare_account_id
environment = "production"
hostname = module.domain.fqdn
service = cloudflare_worker.worker.name
zone_id = data.cloudflare_zone.immich_app.zone_id
}
module "domain" {
source = "git::https://github.com/immich-app/devtools.git//tf/shared/modules/domain?ref=main"
app_name = var.app_name
stage = var.stage
env = var.env
domain = "immich.cloud"
}
+15
View File
@@ -0,0 +1,15 @@
locals {
tf_state_postgres_conn_str = get_env("TF_VAR_tf_state_postgres_conn_str")
}
remote_state {
backend = "pg"
config = {
conn_str = local.tf_state_postgres_conn_str
}
}
inputs = {
tf_state_postgres_conn_str = local.tf_state_postgres_conn_str
}
+87
View File
@@ -0,0 +1,87 @@
import js from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
import globals from 'globals';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import typescriptEslint from 'typescript-eslint';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default typescriptEslint.config([
eslintPluginUnicorn.configs.recommended,
eslintPluginPrettierRecommended,
js.configs.recommended,
typescriptEslint.configs.recommended,
{
ignores: ['eslint.config.mjs', 'deployment/**', '**/.wrangler/**', 'dist'],
},
{
languageOptions: {
globals: {
...globals.node,
},
parser: typescriptEslint.parser,
ecmaVersion: 5,
sourceType: 'module',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
},
},
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'error',
'unicorn/prevent-abbreviations': 'off',
'unicorn/filename-case': 'off',
'unicorn/no-null': 'off',
'unicorn/prefer-top-level-await': 'off',
'unicorn/prefer-event-target': 'off',
'unicorn/no-thenable': 'off',
'unicorn/import-style': 'off',
'unicorn/prefer-structured-clone': 'off',
'unicorn/no-for-loop': 'off',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'require-await': 'off',
'@typescript-eslint/require-await': 'error',
curly: 2,
'prettier/prettier': 0,
'object-shorthand': ['error', 'always'],
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['.*'],
message: 'Relative imports are not allowed.',
},
],
},
],
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
},
},
{
files: ['**/*.config.ts', '**/*.config.js', '**/*.config.mjs'],
rules: {
'no-restricted-imports': 'off',
'unicorn/prefer-export-from': 'off',
},
},
]);
+39
View File
@@ -0,0 +1,39 @@
{
"name": "immich-services",
"version": "1.0.0",
"description": "Immich Cloudflare Workers monorepo",
"private": true,
"type": "module",
"scripts": {
"build": "pnpm -r build",
"lint": "eslint . --max-warnings 0",
"lint:fix": "pnpm run lint -- --fix",
"format": "prettier --check .",
"format:fix": "prettier --write .",
"test": "pnpm -r test",
"check": "tsc --noEmit && pnpm -r typecheck"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.8.71",
"@cloudflare/workers-types": "^4.20241127.0",
"@eslint/js": "^9.35.0",
"@types/node": "^22.10.5",
"@typescript-eslint/eslint-plugin": "^8.19.0",
"@typescript-eslint/parser": "^8.19.0",
"eslint": "^9.17.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-unicorn": "^61.0.2",
"globals": "^16.3.0",
"prettier": "^3.4.2",
"prettier-plugin-organize-imports": "^4.2.0",
"typescript": "^5.7.2",
"typescript-eslint": "^8.43.0",
"vitest": "^3.0.0",
"wrangler": "^4.35.0"
},
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748",
"engines": {
"pnpm": ">=10.0.0"
}
}
+3487
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
packages:
- 'apps/*'
+16
View File
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2021",
"module": "Node16",
"lib": ["ES2021"],
"moduleResolution": "Node16",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"allowJs": true,
"checkJs": true,
"noEmit": true
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"types": ["@cloudflare/workers-types", "@cloudflare/vitest-pool-workers", "@types/node"],
"composite": true,
"declaration": true,
"declarationMap": true
},
"include": [
"src/**/*.ts",
"apps/*/src/**/*.ts",
"apps/*/worker-configuration.d.ts",
"*.config.ts",
"apps/*/*.config.ts"
],
"exclude": ["node_modules", "**/dist", "**/.wrangler"]
}
+11
View File
@@ -0,0 +1,11 @@
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.toml' },
},
},
},
});