diff --git a/.github/workflows/deploy.app.yml b/.github/workflows/deploy.app.yml new file mode 100644 index 0000000..b3b9ebf --- /dev/null +++ b/.github/workflows/deploy.app.yml @@ -0,0 +1,102 @@ +name: 🚀 Deploy App +on: + workflow_call: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + actions: write + contents: read + +jobs: + lint: + name: ⬣ ESLint + runs-on: ubuntu-22.04 + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: 🍞 Setup Bun + uses: oven-sh/setup-bun@v1 + + - name: 📥 Download deps + run: bun install --frozenLockfile + + - name: 🔬 Lint + run: bun lint + + typecheck: + name: ʦ TypeScript + runs-on: ubuntu-22.04 + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: 🍞 Setup Bun + uses: oven-sh/setup-bun@v1 + + - name: 📥 Download deps + run: bun install --frozenLockfile + + - name: 🔎 Type check + run: bun typecheck + working-directory: apps/app + + playwright: + name: 🎭 Playwright + runs-on: ubuntu-22.04 + timeout-minutes: 60 + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: 🏄 Copy test env vars + run: cp .env.example .env + + - name: 🍞 Setup Bun + uses: oven-sh/setup-bun@v1 + + - name: 📥 Download deps + run: bun install --frozenLockfile + + - name: 📥 Install Playwright Browsers + run: bun test:e2e:install + working-directory: apps/app + + - name: 🎭 Playwright tests + run: bun test:e2e:run + working-directory: apps/app + env: + AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }} + IG_USERNAME: ${{ secrets.IG_USERNAME }} + IG_PASSWORD: ${{ secrets.IG_PASSWORD }} + IG_THREAD_ID: ${{ secrets.IG_THREAD_ID }} + + - name: 📊 Upload report + uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: apps/app/playwright-report/ + retention-days: 30 + + deploy: + name: 🚀 Deploy + runs-on: ubuntu-22.04 + needs: [lint, typecheck, playwright] + + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: 🎈 Setup Fly + uses: superfly/flyctl-actions/setup-flyctl@1.5 + + - name: 🚀 Deploy + if: ${{ github.ref == 'refs/heads/main' }} + working-directory: apps/app + run: fly deploy --remote-only --build-arg COMMIT_SHA=${{ github.sha }} + env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/.github/workflows/deploy.scheduler.yml b/.github/workflows/deploy.scheduler.yml new file mode 100644 index 0000000..985ee5b --- /dev/null +++ b/.github/workflows/deploy.scheduler.yml @@ -0,0 +1,61 @@ +name: 🚀 Deploy Scheduler +on: + workflow_call: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + actions: write + contents: read + +jobs: + lint: + name: ⬣ ESLint + runs-on: ubuntu-22.04 + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: 🍞 Setup Bun + uses: oven-sh/setup-bun@v1 + + - name: 📥 Download deps + run: bun install --frozenLockfile + + - name: 🔬 Lint + run: bun lint + + typecheck: + name: ʦ TypeScript + runs-on: ubuntu-22.04 + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: 🍞 Setup Bun + uses: oven-sh/setup-bun@v1 + + - name: 📥 Download deps + run: bun install --frozenLockfile + + - name: 🔎 Type check + run: bun typecheck + working-directory: apps/scheduler + + deploy: + name: 🚀 Deploy + runs-on: ubuntu-22.04 + needs: [lint, typecheck] + + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: 🚀 Deploy + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + workingDirectory: 'apps/scheduler' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e68735e..2522848 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -14,78 +14,9 @@ permissions: contents: read jobs: - lint: - name: ⬣ ESLint - runs-on: ubuntu-22.04 - steps: - - name: ⬇️ Checkout repo - uses: actions/checkout@v4 - - - name: 🍞 Setup Bun - uses: oven-sh/setup-bun@v1 - - - name: 📥 Download deps - run: bun install --frozenLockfile - - - name: 🔬 Lint - run: bun lint - - typecheck: - name: ʦ TypeScript - runs-on: ubuntu-22.04 - steps: - - name: ⬇️ Checkout repo - uses: actions/checkout@v4 - - - name: 🍞 Setup Bun - uses: oven-sh/setup-bun@v1 - - - name: 📥 Download deps - run: bun install --frozenLockfile - - - name: 🔎 Type check - run: bun typecheck - - playwright: - name: 🎭 Playwright - runs-on: ubuntu-22.04 - timeout-minutes: 60 - steps: - - name: ⬇️ Checkout repo - uses: actions/checkout@v4 - - - name: 🏄 Copy test env vars - run: cp .env.example .env - - - name: 🍞 Setup Bun - uses: oven-sh/setup-bun@v1 - - - name: 📥 Download deps - run: bun install --frozenLockfile - - - name: 📥 Install Playwright Browsers - run: bun test:e2e:install - - - name: 🎭 Playwright tests - run: bunx playwright test - env: - AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }} - IG_USERNAME: ${{ secrets.IG_USERNAME }} - IG_PASSWORD: ${{ secrets.IG_PASSWORD }} - IG_THREAD_ID: ${{ secrets.IG_THREAD_ID }} - - - name: 📊 Upload report - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 - deploy: name: 🚀 Deploy runs-on: ubuntu-22.04 - needs: [lint, typecheck, playwright] # only build/deploy branches on pushes if: ${{ github.event_name == 'push' }} @@ -93,19 +24,19 @@ jobs: - name: ⬇️ Checkout repo uses: actions/checkout@v4 - - name: 👀 Read app name - uses: SebRollen/toml-action@v1.2.0 - id: app_name - with: - file: 'fly.toml' - field: 'app' - - - name: 🎈 Setup Fly - uses: superfly/flyctl-actions/setup-flyctl@1.5 - - - name: 🚀 Deploy - if: ${{ github.ref == 'refs/heads/main' }} - run: - flyctl deploy --remote-only --build-arg COMMIT_SHA=${{ github.sha }} - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + - name: 🧮 Determine Changes + id: changed-files + run: | + echo "::set-output name=changed-files::$(git diff --name-only ${{ github.event.before }} ${{ github.sha }})" + + - name: 🚀 Deploy Scheduler + uses: ./.github/workflows/deploy.scheduler.yml + if: + ${{ contains(steps.changed-files.outputs.changed-files, + 'apps/scheduler/') }} + + - name: 🚀 Deploy App + uses: ./.github/workflows/deploy.app.yml + if: + ${{ contains(steps.changed-files.outputs.changed-files, 'apps/app/') + }} diff --git a/.gitignore b/.gitignore index 4f9a5e4..92d139f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,14 @@ node_modules mnt .env -/test-results/ -/playwright-report/ -/blob-report/ -/playwright/.cache/ -/trace +# Playwright +test-results/ +playwright-report/ +blob-report/ +playwright/.cache/ +trace *.zip + +# Wrangler +.wrangler +.dev.vars diff --git a/.dockerignore b/apps/app/.dockerignore similarity index 100% rename from .dockerignore rename to apps/app/.dockerignore diff --git a/.env.example b/apps/app/.env.example similarity index 100% rename from .env.example rename to apps/app/.env.example diff --git a/Dockerfile b/apps/app/Dockerfile similarity index 100% rename from Dockerfile rename to apps/app/Dockerfile diff --git a/TODO.md b/apps/app/TODO.md similarity index 100% rename from TODO.md rename to apps/app/TODO.md diff --git a/docker-compose.yml b/apps/app/docker-compose.yml similarity index 100% rename from docker-compose.yml rename to apps/app/docker-compose.yml diff --git a/fly.toml b/apps/app/fly.toml similarity index 100% rename from fly.toml rename to apps/app/fly.toml diff --git a/apps/app/package.json b/apps/app/package.json new file mode 100644 index 0000000..395fc6e --- /dev/null +++ b/apps/app/package.json @@ -0,0 +1,25 @@ +{ + "name": "@daily-dad-jokes-ig/app", + "private": true, + "type": "module", + "module": "src/entry.ts", + "scripts": { + "dev": "bun --watch .", + "start": "bun .", + "deploy": "fly deploy --remote-only", + "typecheck": "tsc", + "test:e2e:dev": "playwright test --ui", + "test:e2e:run": "cross-env CI=true playwright test", + "test:e2e:install": "npx playwright install --with-deps chromium" + }, + "dependencies": { + "@playwright/test": "^1.42.1", + "cheerio": "^1.0.0-rc.12", + "cross-env": "^7.0.3", + "dotenv": "^16.4.5", + "zod": "^3.22.4" + }, + "devDependencies": { + "typescript": "^5.4.3" + } +} diff --git a/apps/app/playwright.config.js b/apps/app/playwright.config.js new file mode 100644 index 0000000..4be2a48 --- /dev/null +++ b/apps/app/playwright.config.js @@ -0,0 +1,50 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var test_1 = require("@playwright/test"); +require("dotenv/config"); +require("./src/env"); +var PORT = process.env.PORT; +exports.default = (0, test_1.defineConfig)({ + testDir: './tests/e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: 'http://localhost:3000', + trace: 'on-first-retry', + locale: 'en-US', + timezoneId: 'Europe/Vilnius', + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + }, + projects: [ + { name: 'setup', testMatch: /.*\.setup\.ts/ }, + { + name: 'chromium', + use: __assign(__assign({}, test_1.devices['Desktop Chrome']), { storageState: 'mnt/auth.json' }), + dependencies: ['setup'], + }, + ], + webServer: { + command: 'bun start', + url: "http://localhost:".concat(PORT, "/healthcheck"), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { + PORT: PORT, + }, + }, +}); diff --git a/playwright.config.ts b/apps/app/playwright.config.ts similarity index 100% rename from playwright.config.ts rename to apps/app/playwright.config.ts diff --git a/src/entry.ts b/apps/app/src/entry.ts similarity index 100% rename from src/entry.ts rename to apps/app/src/entry.ts diff --git a/src/env.ts b/apps/app/src/env.ts similarity index 100% rename from src/env.ts rename to apps/app/src/env.ts diff --git a/src/modules/fs/disk-file.ts b/apps/app/src/modules/fs/disk-file.ts similarity index 100% rename from src/modules/fs/disk-file.ts rename to apps/app/src/modules/fs/disk-file.ts diff --git a/src/modules/fs/persisted-volume-file-path.ts b/apps/app/src/modules/fs/persisted-volume-file-path.ts similarity index 100% rename from src/modules/fs/persisted-volume-file-path.ts rename to apps/app/src/modules/fs/persisted-volume-file-path.ts diff --git a/src/modules/http/authentication.ts b/apps/app/src/modules/http/authentication.ts similarity index 100% rename from src/modules/http/authentication.ts rename to apps/app/src/modules/http/authentication.ts diff --git a/src/modules/http/http-rate-limiter.ts b/apps/app/src/modules/http/http-rate-limiter.ts similarity index 100% rename from src/modules/http/http-rate-limiter.ts rename to apps/app/src/modules/http/http-rate-limiter.ts diff --git a/src/modules/http/http-router.ts b/apps/app/src/modules/http/http-router.ts similarity index 100% rename from src/modules/http/http-router.ts rename to apps/app/src/modules/http/http-router.ts diff --git a/src/routes.ts b/apps/app/src/routes.ts similarity index 100% rename from src/routes.ts rename to apps/app/src/routes.ts diff --git a/src/routes/healthcheck.ts b/apps/app/src/routes/healthcheck.ts similarity index 100% rename from src/routes/healthcheck.ts rename to apps/app/src/routes/healthcheck.ts diff --git a/src/routes/jokes.ts b/apps/app/src/routes/jokes.ts similarity index 100% rename from src/routes/jokes.ts rename to apps/app/src/routes/jokes.ts diff --git a/src/services/instagram.ts b/apps/app/src/services/instagram.ts similarity index 100% rename from src/services/instagram.ts rename to apps/app/src/services/instagram.ts diff --git a/src/services/jokes.ts b/apps/app/src/services/jokes.ts similarity index 100% rename from src/services/jokes.ts rename to apps/app/src/services/jokes.ts diff --git a/tests/e2e/instagram-auth.setup.ts b/apps/app/tests/e2e/instagram-auth.setup.ts similarity index 100% rename from tests/e2e/instagram-auth.setup.ts rename to apps/app/tests/e2e/instagram-auth.setup.ts diff --git a/tests/e2e/smoke.spec.ts b/apps/app/tests/e2e/smoke.spec.ts similarity index 100% rename from tests/e2e/smoke.spec.ts rename to apps/app/tests/e2e/smoke.spec.ts diff --git a/apps/app/tsconfig.json b/apps/app/tsconfig.json new file mode 100644 index 0000000..8ae2e0b --- /dev/null +++ b/apps/app/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/scheduler/package.json b/apps/scheduler/package.json new file mode 100644 index 0000000..efba011 --- /dev/null +++ b/apps/scheduler/package.json @@ -0,0 +1,15 @@ +{ + "name": "@daily-dad-jokes-ig/scheduler", + "private": true, + "scripts": { + "dev": "wrangler dev", + "start": "wrangler dev", + "deploy": "wrangler deploy", + "typecheck": "tsc" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240329.0", + "typescript": "^5.4.3", + "wrangler": "^3.0.0" + } +} diff --git a/apps/scheduler/src/index.ts b/apps/scheduler/src/index.ts new file mode 100644 index 0000000..c1e71a8 --- /dev/null +++ b/apps/scheduler/src/index.ts @@ -0,0 +1,15 @@ +export interface Env { + BASE_URL: string + API_TOKEN: string +} + +export default { + async scheduled(event: ScheduledEvent, env: Env): Promise { + const resp = await fetch(`${env.BASE_URL}/jokes`, { + method: 'POST', + headers: { Authorization: env.API_TOKEN }, + }) + const wasSuccessful = resp.ok ? 'success' : 'fail' + console.log(`trigger fired at ${event.cron}: ${wasSuccessful}`) + }, +} diff --git a/apps/scheduler/tsconfig.json b/apps/scheduler/tsconfig.json new file mode 100644 index 0000000..e2d4213 --- /dev/null +++ b/apps/scheduler/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["@cloudflare/workers-types/2023-07-01"] + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/scheduler/wrangler.toml b/apps/scheduler/wrangler.toml new file mode 100644 index 0000000..8b7559f --- /dev/null +++ b/apps/scheduler/wrangler.toml @@ -0,0 +1,9 @@ +name = "daily-dad-jokes-ig-scheduler" +main = "src/index.ts" +compatibility_date = "2024-03-29" + +# Cron Triggers +# Docs: https://developers.cloudflare.com/workers/platform/triggers/cron-triggers/ +# Configuration: https://developers.cloudflare.com/workers/wrangler/configuration/#triggers +[triggers] +crons = ["0 9 * * *"] diff --git a/bun.lockb b/bun.lockb index fe8bdc9..c27e51a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 72af27e..c4e4d53 100644 --- a/package.json +++ b/package.json @@ -1,36 +1,16 @@ { - "name": "daily-dad-jokes-ig", - "type": "module", - "module": "src/entry.ts", + "name": "daily-dad-jokes-ig-monorepo", + "private": true, "scripts": { - "dev": "bun --watch .", - "start": "bun .", "lint": "eslint .", "lint:fix": "eslint . --fix", - "format": "prettier --write .", - "typecheck": "tsc", - "auth:gen": "playwright codegen https://instagram.com --save-storage=mnt/auth.json", - "auth:verify": "playwright codegen https://instagram.com --load-storage=mnt/auth.json", - "deploy": "fly deploy --remote-only", - "test:e2e:dev": "playwright test --ui", - "test:e2e:run": "cross-env CI=true playwright test", - "test:e2e:install": "npx playwright install --with-deps chromium", - "validate": "run-p lint typecheck test:e2e:run" + "format": "prettier --write ." }, "eslintIgnore": [ "/node_modules", - "/build", - "/public/build", - "/playwright-report", - "/trace", - "/server-build" + "playwright-report", + "trace" ], - "dependencies": { - "@playwright/test": "^1.42.1", - "cheerio": "^1.0.0-rc.12", - "dotenv": "^16.4.5", - "zod": "^3.22.4" - }, "devDependencies": { "@types/bun": "latest", "@typescript-eslint/eslint-plugin": "latest", @@ -41,5 +21,8 @@ "npm-run-all": "^4.1.5", "prettier": "^3.2.5", "typescript": "^5.4.3" - } + }, + "workspaces": [ + "apps/*" + ] } diff --git a/tsconfig.json b/tsconfig.base.json similarity index 82% rename from tsconfig.json rename to tsconfig.base.json index 382fc5c..a9cbf5b 100644 --- a/tsconfig.json +++ b/tsconfig.base.json @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { // Enable latest features "lib": ["ESNext"], @@ -17,6 +18,7 @@ "strict": true, "skipLibCheck": true, "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, // Stricter "noUnusedLocals": true, @@ -24,5 +26,6 @@ "noPropertyAccessFromIndexSignature": true, "noUncheckedIndexedAccess": true, "noImplicitReturns": true - } + }, + "exclude": ["node_modules"] }