Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions .github/workflows/ghcr_web_docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: Publish AppFlowy Web to GHCR
run-name: Publish AppFlowy Web (${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name || github.ref_name }})

on:
push:
tags:
- 'v*.*.*'
- '*.*.*'
workflow_dispatch:
inputs:
tag_name:
description: 'Tag for Docker image (example: v0.9.1-patch1)'
required: true
type: string
build_arm64:
description: 'Build ARM64 in addition to AMD64'
required: false
type: boolean
default: true
tag_latest:
description: 'Also push the latest tag'
required: false
type: boolean
default: false

permissions:
contents: read
packages: write

jobs:
publish:
runs-on: ubuntu-22.04

steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Set variables
id: vars
run: |
OWNER_LC="${GITHUB_REPOSITORY_OWNER,,}"

if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
IMAGE_TAG="${{ github.event.inputs.tag_name }}"
else
IMAGE_TAG="${GITHUB_REF#refs/tags/}"
fi

if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ github.event.inputs.build_arm64 }}" != "true" ]; then
PLATFORMS="linux/amd64"
else
PLATFORMS="linux/amd64,linux/arm64"
fi

if [ "${{ github.event_name }}" = "push" ] || [ "${{ github.event.inputs.tag_latest }}" = "true" ]; then
PUSH_LATEST="true"
else
PUSH_LATEST="false"
fi

IMAGE_BASE="ghcr.io/$OWNER_LC/appflowy_web"
TAGS="$IMAGE_BASE:$IMAGE_TAG"
if [ "$PUSH_LATEST" = "true" ]; then
TAGS="$TAGS"$'\n'"$IMAGE_BASE:latest"
fi

{
echo "owner_lc=$OWNER_LC"
echo "image_tag=$IMAGE_TAG"
echo "platforms=$PLATFORMS"
echo "tags<<EOF"
printf '%s\n' "$TAGS"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push appflowy_web
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile.ssr
platforms: ${{ steps.vars.outputs.platforms }}
push: true
provenance: false
tags: ${{ steps.vars.outputs.tags }}
build-args: |
VERSION=${{ steps.vars.outputs.image_tag }}
cache-from: type=gha,scope=ghcr-appflowy-web
cache-to: type=gha,mode=max,scope=ghcr-appflowy-web
62 changes: 52 additions & 10 deletions .github/workflows/playwright-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
name: "Build"
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Setup Node.js
uses: actions/setup-node@v4
Expand Down Expand Up @@ -98,7 +98,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Setup Node.js
uses: actions/setup-node@v4
Expand All @@ -110,15 +110,47 @@ jobs:
with:
version: ${{ env.PNPM_VERSION }}

- name: Check required secrets for premium E2E
id: prereq
shell: bash
env:
CI_TOKEN: ${{ secrets.CI_TOKEN }}
CI_OPENAI_API_KEY: ${{ secrets.CI_OPENAI_API_KEY }}
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
run: |
missing=()

[ -z "$CI_TOKEN" ] && missing+=("CI_TOKEN")
[ -z "$CI_OPENAI_API_KEY" ] && missing+=("CI_OPENAI_API_KEY")
[ -z "$DOCKER_HUB_USERNAME" ] && missing+=("DOCKER_HUB_USERNAME")
[ -z "$DOCKER_HUB_ACCESS_TOKEN" ] && missing+=("DOCKER_HUB_ACCESS_TOKEN")

if [ ${#missing[@]} -gt 0 ]; then
echo "ready=false" >> "$GITHUB_OUTPUT"
echo "missing=${missing[*]}" >> "$GITHUB_OUTPUT"
{
echo "## Playwright E2E skipped"
echo ""
echo "Missing required secrets: ${missing[*]}"
echo ""
echo "Set these repository secrets to enable this workflow in your fork."
} >> "$GITHUB_STEP_SUMMARY"
else
echo "ready=true" >> "$GITHUB_OUTPUT"
fi

- name: Checkout AppFlowy-Cloud-Premium
uses: actions/checkout@v4
if: steps.prereq.outputs.ready == 'true'
uses: actions/checkout@v5
with:
repository: AppFlowy-IO/AppFlowy-Cloud-Premium
ref: main
token: ${{ secrets.CI_TOKEN }}
token: ${{ secrets.CI_TOKEN || github.token }}
path: AppFlowy-Cloud-Premium

- name: Setup AppFlowy Cloud
if: steps.prereq.outputs.ready == 'true'
working-directory: AppFlowy-Cloud-Premium
env:
OPENAI_KEY: ${{ secrets.CI_OPENAI_API_KEY }}
Expand All @@ -133,13 +165,15 @@ jobs:
echo 'APPFLOWY_PAGE_HISTORY_ENABLE=true' >> .env

- name: Log in to Docker Hub
if: steps.prereq.outputs.ready == 'true'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

# Overlap Docker pull (network I/O) with pnpm install + Playwright browser install
- name: Install deps and pull Docker images (parallel)
if: steps.prereq.outputs.ready == 'true'
env:
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
APPFLOWY_AI_VERSION: ${{ env.CLOUD_VERSION }}
Expand All @@ -156,30 +190,34 @@ jobs:
wait $DOCKER_PID

- name: Cache Playwright browsers
if: steps.prereq.outputs.ready == 'true'
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
if: steps.prereq.outputs.ready == 'true' && steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium

- name: Install Playwright deps (cached browsers)
if: steps.playwright-cache.outputs.cache-hit == 'true'
if: steps.prereq.outputs.ready == 'true' && steps.playwright-cache.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium

- name: Setup environment
if: steps.prereq.outputs.ready == 'true'
run: cp deploy.env .env

- name: Download build artifact
if: steps.prereq.outputs.ready == 'true'
uses: actions/download-artifact@v4
with:
name: build-dist
path: dist/

- name: Start Docker services
if: steps.prereq.outputs.ready == 'true'
working-directory: AppFlowy-Cloud-Premium
env:
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
Expand All @@ -203,14 +241,17 @@ jobs:
)

- name: Setup Bun
if: steps.prereq.outputs.ready == 'true'
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install Bun SSR dependencies
if: steps.prereq.outputs.ready == 'true'
run: bun install cheerio pino pino-pretty

- name: Start SSR server
if: steps.prereq.outputs.ready == 'true'
run: |
pnpm run dev:server &
echo $! > ssr-server.pid
Expand All @@ -221,13 +262,14 @@ jobs:
done' && echo "SSR server is ready" || (echo "SSR server failed to start" && exit 1)

- name: Run ${{ matrix.test-group.name }} tests
if: steps.prereq.outputs.ready == 'true'
run: pnpm exec playwright test --workers=2 ${{ matrix.test-group.spec }}
env:
CI: true
BASE_URL: http://localhost:3000

- name: Test summary
if: always()
if: always() && steps.prereq.outputs.ready == 'true'
run: |
if [ ! -f playwright-report/report.json ]; then
echo "## ${{ matrix.test-group.name }} — no report generated" >> $GITHUB_STEP_SUMMARY
Expand Down Expand Up @@ -271,7 +313,7 @@ jobs:
" >> $GITHUB_STEP_SUMMARY

- name: Upload test results
if: always()
if: always() && steps.prereq.outputs.ready == 'true'
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ matrix.test-group.name }}
Expand All @@ -280,7 +322,7 @@ jobs:
retention-days: 7

- name: Upload test traces
if: failure()
if: failure() && steps.prereq.outputs.ready == 'true'
uses: actions/upload-artifact@v4
with:
name: playwright-traces-${{ matrix.test-group.name }}
Expand All @@ -289,7 +331,7 @@ jobs:
retention-days: 3

- name: Cleanup
if: always()
if: always() && steps.prereq.outputs.ready == 'true'
run: |
if [ -f ssr-server.pid ]; then
kill $(cat ssr-server.pid) 2>/dev/null || true
Expand Down
4 changes: 2 additions & 2 deletions docker/docker-compose.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.8'

services:
appflowy-web:
image: appflowy/appflowy_web:latest
image: ${APPFLOWY_WEB_IMAGE:-appflowyinc/appflowy_web}:${APPFLOWY_WEB_VERSION:-latest}
ports:
- "80:80"
environment:
Expand All @@ -11,4 +11,4 @@ services:
- APPFLOWY_BASE_URL=https://your-backend.example.com
- APPFLOWY_GOTRUE_BASE_URL=https://your-backend.example.com/gotrue
- APPFLOWY_WS_BASE_URL=wss://your-backend.example.com/ws/v2
restart: unless-stopped
restart: unless-stopped
1 change: 1 addition & 0 deletions src/@types/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3148,6 +3148,7 @@
"continueWithDiscord": "Continue with Discord",
"continueWithApple": "Continue with Apple ",
"continueWithSaml": "Continue with SSO",
"continueWithOidc": "Continue with OIDC",
"continueWithSso": "Continue",
"ssoLogin": "SSO Login",
"ssoLoginDescription": "Enter your work email to sign in with your organization's identity provider.",
Expand Down
1 change: 1 addition & 0 deletions src/application/services/domains/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export {
signInAppleWithRedirect as signInApple,
signInGithubWithRedirect as signInGithub,
signInDiscordWithRedirect as signInDiscord,
signInAuthentikWithRedirect as signInAuthentik,
signInSamlWithRedirect as signInSaml,
signInWithPasswordWithRedirect as signInWithPassword,
signUpWithPasswordWithRedirect as signUpWithPassword,
Expand Down
7 changes: 7 additions & 0 deletions src/application/services/js-services/cached-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
changePassword,
forgotPassword,
signInApple,
signInAuthentik,
signInDiscord,
signInGithub,
signInGoogle,
Expand Down Expand Up @@ -461,6 +462,7 @@ export {
signInApple,
signInGithub,
signInDiscord,
signInAuthentik,
signInSaml,
};

Expand All @@ -484,6 +486,11 @@ export async function signInDiscordWithRedirect(params: { redirectTo: string })
return signInDiscord(AUTH_CALLBACK_URL);
}

export async function signInAuthentikWithRedirect(params: { redirectTo: string }) {
saveRedirectTo(params.redirectTo);
return signInAuthentik(AUTH_CALLBACK_URL);
}

export async function signInSamlWithRedirect(params: { redirectTo: string; domain: string }): Promise<void> {
saveRedirectTo(params.redirectTo);
return signInSaml(AUTH_CALLBACK_URL, params.domain);
Expand Down
Loading
Loading