# justfile — libnovel-v2 task runner # Install just: https://just.systems scraper_dir := "scraper" ui_dir := "ui" ios_dir := "ios/LibNovel" ios_scheme := "LibNovel" ios_sim := "platform=iOS Simulator,name=iPhone 17" ios_spm := ".spm-cache" runner_temp := env_var_or_default("RUNNER_TEMP", "/tmp") # ─── Build ──────────────────────────────────────────────────────────────────── # Build the scraper binary build: cd {{scraper_dir}} && go build -o bin/scraper ./cmd/scraper # Build and verify all Go packages compile cleanly build-all: cd {{scraper_dir}} && go build ./... # ─── Tests ──────────────────────────────────────────────────────────────────── # Run unit tests only (no integration services required) test: cd {{scraper_dir}} && go test -race -count=1 -timeout=60s ./... # Run integration tests (requires MinIO, PocketBase, optional Browserless) # Override env vars as needed, e.g.: # just test-integration MINIO_ENDPOINT=localhost:9000 test-integration: cd {{scraper_dir}} && go test -v -tags integration -timeout 600s ./... # Run unit + integration tests test-all: test test-integration # Run a specific package's integration tests, e.g.: # just test-pkg internal/storage test-pkg pkg: cd {{scraper_dir}} && go test -v -tags integration -timeout 600s ./{{pkg}}/... # Run end-to-end tests against live services. # All services must be running first (docker compose up -d or just e2e-up). # Override env vars as needed, e.g.: # just test-e2e SCRAPER_URL=http://localhost:8080 KOKORO_VOICE=af_bella test-e2e \ browserless_url="http://localhost:3030" \ minio_endpoint="localhost:9000" \ pocketbase_url="http://localhost:8090" \ scraper_url="http://localhost:8080": cd {{scraper_dir}} && \ BROWSERLESS_URL={{browserless_url}} \ MINIO_ENDPOINT={{minio_endpoint}} \ POCKETBASE_URL={{pocketbase_url}} \ SCRAPER_URL={{scraper_url}} \ go test -v -tags integration -timeout 900s ./internal/e2e/... # Start all services required for e2e tests, then run them e2e: up test-e2e # ─── Code quality ───────────────────────────────────────────────────────────── # Run go vet on all packages (including integration build tag) lint: cd {{scraper_dir}} && go vet ./... cd {{scraper_dir}} && go vet -tags integration ./... # ─── UI ─────────────────────────────────────────────────────────────────────── # Type-check the SvelteKit UI ui-check: cd {{ui_dir}} && npx svelte-check # Start the SvelteKit dev server ui-dev: cd {{ui_dir}} && npm run dev # Install UI dependencies ui-install: cd {{ui_dir}} && npm install # Build the UI for production ui-build: cd {{ui_dir}} && npm run build # ─── iOS ────────────────────────────────────────────────────────────────────── # Regenerate LibNovel.xcodeproj from project.yml (run after structural changes) ios-gen: cd {{ios_dir}} && xcodegen generate --spec project.yml --project . # Resolve SPM package dependencies (cached to {{ios_spm}}) ios-resolve: cd {{ios_dir}} && xcodebuild \ -project {{ios_scheme}}.xcodeproj \ -scheme {{ios_scheme}} \ -resolvePackageDependencies \ -clonedSourcePackagesDirPath {{ios_spm}} # Build the iOS app for the simulator (no signing required) # Runs ios-gen first to ensure the project is up to date. ios-build: ios-gen ios-resolve cd {{ios_dir}} && set -o pipefail && xcodebuild \ -project {{ios_scheme}}.xcodeproj \ -scheme {{ios_scheme}} \ -configuration Debug \ -destination 'generic/platform=iOS Simulator' \ -clonedSourcePackagesDirPath {{ios_spm}} \ CODE_SIGNING_ALLOWED=NO \ | xcpretty || xcodebuild \ -project {{ios_scheme}}.xcodeproj \ -scheme {{ios_scheme}} \ -configuration Debug \ -destination 'generic/platform=iOS Simulator' \ -clonedSourcePackagesDirPath {{ios_spm}} \ CODE_SIGNING_ALLOWED=NO # Run unit tests on the simulator # Runs ios-gen first to ensure the project is up to date. ios-test: ios-gen ios-resolve cd {{ios_dir}} && set -o pipefail && xcodebuild test \ -project {{ios_scheme}}.xcodeproj \ -scheme {{ios_scheme}} \ -configuration Debug \ -destination '{{ios_sim}}' \ -clonedSourcePackagesDirPath {{ios_spm}} \ CODE_SIGNING_ALLOWED=NO \ | xcpretty --report junit --output test-results.xml || true # Archive a signed Release build (requires valid signing identity in keychain). # Output: {{runner_temp}}/LibNovel.xcarchive # Typically called from CI after importing certificate + provisioning profile. # Usage: just ios-archive ios-archive team_id profile_name: ios-gen ios-resolve cd {{ios_dir}} && xcodebuild archive \ -project {{ios_scheme}}.xcodeproj \ -scheme {{ios_scheme}} \ -configuration Release \ -destination 'generic/platform=iOS' \ -clonedSourcePackagesDirPath {{ios_spm}} \ -archivePath {{runner_temp}}/LibNovel.xcarchive \ CODE_SIGN_STYLE=Manual \ CODE_SIGN_IDENTITY="Apple Distribution" \ DEVELOPMENT_TEAM="{{team_id}}" \ PROVISIONING_PROFILE_SPECIFIER="{{profile_name}}" # Export an IPA from the archive produced by ios-archive. # Requires ios/LibNovel/ExportOptions.plist. # Output: {{runner_temp}}/ipa/LibNovel.ipa ios-export: cd {{ios_dir}} && xcodebuild -exportArchive \ -archivePath {{runner_temp}}/LibNovel.xcarchive \ -exportPath {{runner_temp}}/ipa \ -exportOptionsPlist ExportOptions.plist # Set the build number (CFBundleVersion) in project.yml before archiving. # Usage: just ios-set-build-number 42 ios-set-build-number number: cd {{ios_dir}} && sed -i '' \ 's/CURRENT_PROJECT_VERSION: .*/CURRENT_PROJECT_VERSION: {{number}}/' \ project.yml # Upload the exported IPA to TestFlight via App Store Connect API. # Requires env vars: ASC_KEY_ID, ASC_ISSUER_ID, ASC_PRIVATE_KEY_PATH # The private key (.p8 file) must be present at ASC_PRIVATE_KEY_PATH. ios-upload: xcrun altool --upload-app \ --type ios \ --file {{runner_temp}}/ipa/LibNovel.ipa \ --apiKey "$ASC_KEY_ID" \ --apiIssuer "$ASC_ISSUER_ID" # ─── Docker Compose ─────────────────────────────────────────────────────────── # Start all services (browserless, kokoro, scraper, minio, pocketbase) up: docker compose up -d # Stop all services down: docker compose down # Tail logs for all services logs: docker compose logs -f # Tail logs for a specific service, e.g.: just logs-service scraper logs-service service: docker compose logs -f {{service}} # Rebuild and restart a specific service restart service: docker compose up -d --build {{service}} # ─── Local dev: individual services ────────────────────────────────────────── # Start only PocketBase (for local storage testing) pb-up: docker compose up -d pocketbase # Start only MinIO (for local storage testing) minio-up: docker compose up -d minio # Start only Browserless (for local scraping tests) browserless-up: docker compose up -d browserless # Start storage backends only (MinIO + PocketBase) storage-up: docker compose up -d minio pocketbase # ─── Convenience ───────────────────────────────────────────────────────────── # Show status of all docker compose services status: docker compose ps # Remove all stopped containers and unused images prune: docker compose down --remove-orphans docker image prune -f # One-shot scrape of the full catalogue (requires services to be running) scrape-run: build cd {{scraper_dir}} && ./bin/scraper run # One-shot scrape of a single book URL, e.g.: # just scrape-book https://novelfire.net/book/my-novel scrape-book url: build cd {{scraper_dir}} && ./bin/scraper run --url {{url}} # Start the HTTP server serve: build cd {{scraper_dir}} && ./bin/scraper serve