Compare commits

..

6 Commits

Author SHA1 Message Date
Yifeng Wang
3d2d399796 refactor: extract common builder 2025-05-29 16:13:48 +08:00
Yifeng Wang
6483f36723 refactor: use path builder 2025-05-29 16:13:48 +08:00
Yifeng Wang
df2ecf2bec feat(editor): support connector dom renderer 2025-05-29 16:13:48 +08:00
Yifeng Wang
148c718a12 fix: review 2025-05-29 16:13:48 +08:00
Yifeng Wang
c4af1e77d0 fix: test 2025-05-29 16:13:48 +08:00
Yifeng Wang
6a0eb80903 feat(editor): support triangle and diamond shape in shape dom renderer 2025-05-29 16:13:48 +08:00
3123 changed files with 51615 additions and 218430 deletions

View File

@@ -5,14 +5,7 @@ rustflags = ["-C", "target-feature=+crt-static"]
[target.'cfg(target_os = "linux")'] [target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-args=-Wl,--warn-unresolved-symbols"] rustflags = ["-C", "link-args=-Wl,--warn-unresolved-symbols"]
[target.'cfg(target_os = "macos")'] [target.'cfg(target_os = "macos")']
rustflags = [ rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup,-no_fixup_chains", "-C", "link-args=-all_load", "-C", "link-args=-weak_framework ScreenCaptureKit"]
"-C",
"link-args=-Wl,-undefined,dynamic_lookup,-no_fixup_chains",
"-C",
"link-args=-all_load",
"-C",
"link-args=-weak_framework ScreenCaptureKit",
]
# https://sourceware.org/bugzilla/show_bug.cgi?id=21032 # https://sourceware.org/bugzilla/show_bug.cgi?id=21032
# https://sourceware.org/bugzilla/show_bug.cgi?id=21031 # https://sourceware.org/bugzilla/show_bug.cgi?id=21031
# https://github.com/rust-lang/rust/issues/134820 # https://github.com/rust-lang/rust/issues/134820

View File

@@ -6,6 +6,7 @@ yarn install
# Build Server Dependencies # Build Server Dependencies
yarn affine @affine/server-native build yarn affine @affine/server-native build
yarn affine @affine/reader build
# Create database # Create database
yarn affine @affine/server prisma migrate reset -f yarn affine @affine/server prisma migrate reset -f

View File

@@ -2,8 +2,6 @@ version: '3.8'
services: services:
app: app:
security_opt:
- no-new-privileges:true
image: mcr.microsoft.com/devcontainers/base:bookworm image: mcr.microsoft.com/devcontainers/base:bookworm
volumes: volumes:
- ../..:/workspaces:cached - ../..:/workspaces:cached
@@ -27,7 +25,7 @@ services:
image: redis image: redis
indexer: indexer:
image: manticoresearch/manticore:${MANTICORE_VERSION:-10.1.0} image: manticoresearch/manticore:${MANTICORE_VERSION:-9.3.2}
ulimits: ulimits:
nproc: 65535 nproc: 65535
nofile: nofile:

View File

@@ -12,4 +12,4 @@ DB_DATABASE_NAME=affine
# ELASTIC_PLATFORM=linux/arm64 # ELASTIC_PLATFORM=linux/arm64
# manticoresearch # manticoresearch
MANTICORE_VERSION=10.1.0 MANTICORE_VERSION=9.3.2

View File

@@ -15,7 +15,13 @@ yarn affine cert --install
```bash ```bash
# certificates will be located at `./.docker/dev/certs/${domain}` # certificates will be located at `./.docker/dev/certs/${domain}`
yarn affine cert --domain affine.localhost yarn affine cert --domain dev.affine.fail
``` ```
### 3. Enable nginx service in compose.yml ### 3. Enable dns and nginx service in compose.yml
### 4. Add custom dns server
```bash
echo "nameserver 127.0.0.1" | sudo tee /etc/resolver/dev.affine.fail
```

View File

@@ -18,23 +18,15 @@ services:
ports: ports:
- 6379:6379 - 6379:6379
# https://mailpit.axllent.org/docs/install/docker/ mailhog:
mailpit: image: mailhog/mailhog:latest
image: axllent/mailpit:latest
ports: ports:
- 1025:1025 - 1025:1025
- 8025:8025 - 8025:8025
environment:
MP_MAX_MESSAGES: 5000
MP_DATABASE: /data/mailpit.db
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
volumes:
- mailpit_data:/data
# https://manual.manticoresearch.com/Starting_the_server/Docker # https://manual.manticoresearch.com/Starting_the_server/Docker
manticoresearch: manticoresearch:
image: manticoresearch/manticore:${MANTICORE_VERSION:-10.1.0} image: manticoresearch/manticore:${MANTICORE_VERSION:-9.3.2}
ports: ports:
- 9308:9308 - 9308:9308
ulimits: ulimits:
@@ -81,6 +73,17 @@ services:
# timeout: 10s # timeout: 10s
# retries: 120 # retries: 120
# dns:
# image: strm/dnsmasq
# volumes:
# - ./dnsmasq.conf:/etc/dnsmasq.d/local.conf
# ports:
# - "53:53/udp"
# cap_add:
# - NET_ADMIN
# depends_on:
# - nginx
# nginx: # nginx:
# image: nginx:alpine # image: nginx:alpine
# volumes: # volumes:
@@ -95,5 +98,4 @@ networks:
volumes: volumes:
postgres_data: postgres_data:
manticoresearch_data: manticoresearch_data:
mailpit_data:
elasticsearch_data: elasticsearch_data:

2
.docker/dev/dnsmasq.conf Normal file
View File

@@ -0,0 +1,2 @@
log-queries
address=/dev.affine.fail/127.0.0.1

View File

@@ -1,7 +1,7 @@
name: affine name: affine
services: services:
affine: affine:
image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable} image: ghcr.io/toeverything/affine-graphql:${AFFINE_REVISION:-stable}
container_name: affine_server container_name: affine_server
ports: ports:
- '${PORT:-3010}:3010' - '${PORT:-3010}:3010'
@@ -25,7 +25,7 @@ services:
restart: unless-stopped restart: unless-stopped
affine_migration: affine_migration:
image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable} image: ghcr.io/toeverything/affine-graphql:${AFFINE_REVISION:-stable}
container_name: affine_migration_job container_name: affine_migration_job
volumes: volumes:
# custom configurations # custom configurations

View File

@@ -148,11 +148,6 @@
"description": "Whether allow new registrations.\n@default true", "description": "Whether allow new registrations.\n@default true",
"default": true "default": true
}, },
"allowSignupForOauth": {
"type": "boolean",
"description": "Whether allow new registrations via configured oauth.\n@default true",
"default": true
},
"requireEmailDomainVerification": { "requireEmailDomainVerification": {
"type": "boolean", "type": "boolean",
"description": "Whether require email domain record verification before accessing restricted resources.\n@default false", "description": "Whether require email domain record verification before accessing restricted resources.\n@default false",
@@ -195,11 +190,6 @@
"type": "object", "type": "object",
"description": "Configuration for mailer module", "description": "Configuration for mailer module",
"properties": { "properties": {
"SMTP.name": {
"type": "string",
"description": "Name of the email server (e.g. your domain name)\n@default \"AFFiNE Server\"\n@environment `MAILER_SERVERNAME`",
"default": "AFFiNE Server"
},
"SMTP.host": { "SMTP.host": {
"type": "string", "type": "string",
"description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"\n@environment `MAILER_HOST`", "description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"\n@environment `MAILER_HOST`",
@@ -222,52 +212,12 @@
}, },
"SMTP.sender": { "SMTP.sender": {
"type": "string", "type": "string",
"description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted &lt;noreply@example.com&gt;\")\n@default \"AFFiNE Self Hosted <noreply@example.com>\"\n@environment `MAILER_SENDER`", "description": "Sender of all the emails (e.g. \"AFFiNE Team <noreply@affine.pro>\")\n@default \"\"\n@environment `MAILER_SENDER`",
"default": "AFFiNE Self Hosted <noreply@example.com>" "default": ""
}, },
"SMTP.ignoreTLS": { "SMTP.ignoreTLS": {
"type": "boolean", "type": "boolean",
"description": "Whether ignore email server's TLS certificate verification. Enable it for self-signed certificates.\n@default false\n@environment `MAILER_IGNORE_TLS`", "description": "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.\n@default false\n@environment `MAILER_IGNORE_TLS`",
"default": false
},
"fallbackDomains": {
"type": "array",
"description": "The emails from these domains are always sent using the fallback SMTP server.\n@default []",
"default": []
},
"fallbackSMTP.name": {
"type": "string",
"description": "Name of the fallback email server (e.g. your domain name)\n@default \"AFFiNE Server\"",
"default": "AFFiNE Server"
},
"fallbackSMTP.host": {
"type": "string",
"description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"",
"default": ""
},
"fallbackSMTP.port": {
"type": "number",
"description": "Port of the email server (they commonly are 25, 465 or 587)\n@default 465",
"default": 465
},
"fallbackSMTP.username": {
"type": "string",
"description": "Username used to authenticate the email server\n@default \"\"",
"default": ""
},
"fallbackSMTP.password": {
"type": "string",
"description": "Password used to authenticate the email server\n@default \"\"",
"default": ""
},
"fallbackSMTP.sender": {
"type": "string",
"description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted &lt;noreply@example.com&gt;\")\n@default \"\"",
"default": ""
},
"fallbackSMTP.ignoreTLS": {
"type": "boolean",
"description": "Whether ignore email server's TLS certificate verification. Enable it for self-signed certificates.\n@default false",
"default": false "default": false
} }
} }
@@ -337,42 +287,8 @@
}, },
"config": { "config": {
"type": "object", "type": "object",
"description": "The config for the S3 compatible storage provider.", "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
"properties": { "properties": {
"endpoint": {
"type": "string",
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
},
"region": {
"type": "string",
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
},
"forcePathStyle": {
"type": "boolean",
"description": "Whether to use path-style bucket addressing."
},
"requestTimeoutMs": {
"type": "number",
"description": "Request timeout in milliseconds."
},
"minPartSize": {
"type": "number",
"description": "Minimum multipart part size in bytes."
},
"presign": {
"type": "object",
"description": "Presigned URL behavior configuration.",
"properties": {
"expiresInSeconds": {
"type": "number",
"description": "Expiration time in seconds for presigned URLs."
},
"signContentTypeForPut": {
"type": "boolean",
"description": "Whether to sign Content-Type for presigned PUT."
}
}
},
"credentials": { "credentials": {
"type": "object", "type": "object",
"description": "The credentials for the s3 compatible storage provider.", "description": "The credentials for the s3 compatible storage provider.",
@@ -382,9 +298,6 @@
}, },
"secretAccessKey": { "secretAccessKey": {
"type": "string" "type": "string"
},
"sessionToken": {
"type": "string"
} }
} }
} }
@@ -406,42 +319,8 @@
}, },
"config": { "config": {
"type": "object", "type": "object",
"description": "The config for the S3 compatible storage provider.", "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
"properties": { "properties": {
"endpoint": {
"type": "string",
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
},
"region": {
"type": "string",
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
},
"forcePathStyle": {
"type": "boolean",
"description": "Whether to use path-style bucket addressing."
},
"requestTimeoutMs": {
"type": "number",
"description": "Request timeout in milliseconds."
},
"minPartSize": {
"type": "number",
"description": "Minimum multipart part size in bytes."
},
"presign": {
"type": "object",
"description": "Presigned URL behavior configuration.",
"properties": {
"expiresInSeconds": {
"type": "number",
"description": "Expiration time in seconds for presigned URLs."
},
"signContentTypeForPut": {
"type": "boolean",
"description": "Whether to sign Content-Type for presigned PUT."
}
}
},
"credentials": { "credentials": {
"type": "object", "type": "object",
"description": "The credentials for the s3 compatible storage provider.", "description": "The credentials for the s3 compatible storage provider.",
@@ -451,9 +330,6 @@
}, },
"secretAccessKey": { "secretAccessKey": {
"type": "string" "type": "string"
},
"sessionToken": {
"type": "string"
} }
} }
}, },
@@ -471,7 +347,7 @@
}, },
"urlPrefix": { "urlPrefix": {
"type": "string", "type": "string",
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)" "description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
}, },
"signKey": { "signKey": {
"type": "string", "type": "string",
@@ -532,42 +408,8 @@
}, },
"config": { "config": {
"type": "object", "type": "object",
"description": "The config for the S3 compatible storage provider.", "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
"properties": { "properties": {
"endpoint": {
"type": "string",
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
},
"region": {
"type": "string",
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
},
"forcePathStyle": {
"type": "boolean",
"description": "Whether to use path-style bucket addressing."
},
"requestTimeoutMs": {
"type": "number",
"description": "Request timeout in milliseconds."
},
"minPartSize": {
"type": "number",
"description": "Minimum multipart part size in bytes."
},
"presign": {
"type": "object",
"description": "Presigned URL behavior configuration.",
"properties": {
"expiresInSeconds": {
"type": "number",
"description": "Expiration time in seconds for presigned URLs."
},
"signContentTypeForPut": {
"type": "boolean",
"description": "Whether to sign Content-Type for presigned PUT."
}
}
},
"credentials": { "credentials": {
"type": "object", "type": "object",
"description": "The credentials for the s3 compatible storage provider.", "description": "The credentials for the s3 compatible storage provider.",
@@ -577,9 +419,6 @@
}, },
"secretAccessKey": { "secretAccessKey": {
"type": "string" "type": "string"
},
"sessionToken": {
"type": "string"
} }
} }
} }
@@ -601,42 +440,8 @@
}, },
"config": { "config": {
"type": "object", "type": "object",
"description": "The config for the S3 compatible storage provider.", "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
"properties": { "properties": {
"endpoint": {
"type": "string",
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
},
"region": {
"type": "string",
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
},
"forcePathStyle": {
"type": "boolean",
"description": "Whether to use path-style bucket addressing."
},
"requestTimeoutMs": {
"type": "number",
"description": "Request timeout in milliseconds."
},
"minPartSize": {
"type": "number",
"description": "Minimum multipart part size in bytes."
},
"presign": {
"type": "object",
"description": "Presigned URL behavior configuration.",
"properties": {
"expiresInSeconds": {
"type": "number",
"description": "Expiration time in seconds for presigned URLs."
},
"signContentTypeForPut": {
"type": "boolean",
"description": "Whether to sign Content-Type for presigned PUT."
}
}
},
"credentials": { "credentials": {
"type": "object", "type": "object",
"description": "The credentials for the s3 compatible storage provider.", "description": "The credentials for the s3 compatible storage provider.",
@@ -646,9 +451,6 @@
}, },
"secretAccessKey": { "secretAccessKey": {
"type": "string" "type": "string"
},
"sessionToken": {
"type": "string"
} }
} }
}, },
@@ -666,7 +468,7 @@
}, },
"urlPrefix": { "urlPrefix": {
"type": "string", "type": "string",
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)" "description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
}, },
"signKey": { "signKey": {
"type": "string", "type": "string",
@@ -738,16 +540,6 @@
"description": "Where the server get deployed(FQDN).\n@default \"localhost\"\n@environment `AFFINE_SERVER_HOST`", "description": "Where the server get deployed(FQDN).\n@default \"localhost\"\n@environment `AFFINE_SERVER_HOST`",
"default": "localhost" "default": "localhost"
}, },
"hosts": {
"type": "array",
"description": "Multiple hosts the server will accept requests from.\n@default []",
"default": []
},
"listenAddr": {
"type": "string",
"description": "The address to listen on (e.g., 0.0.0.0 for IPv4, :: for IPv6).\n@default \"0.0.0.0\"\n@environment `LISTEN_ADDR`",
"default": "0.0.0.0"
},
"port": { "port": {
"type": "number", "type": "number",
"description": "Which port the server will listen on.\n@default 3010\n@environment `AFFINE_SERVER_PORT`", "description": "Which port the server will listen on.\n@default 3010\n@environment `AFFINE_SERVER_PORT`",
@@ -764,10 +556,10 @@
"type": "object", "type": "object",
"description": "Configuration for flags module", "description": "Configuration for flags module",
"properties": { "properties": {
"allowGuestDemoWorkspace": { "earlyAccessControl": {
"type": "boolean", "type": "boolean",
"description": "Whether allow guest users to create demo workspaces.\n@default true", "description": "Only allow users with early access features to access the app\n@default false",
"default": true "default": false
} }
} }
}, },
@@ -782,45 +574,6 @@
} }
} }
}, },
"telemetry": {
"type": "object",
"description": "Configuration for telemetry module",
"properties": {
"allowedOrigin": {
"type": "array",
"description": "Allowed origins for telemetry collection.\n@default [\"localhost\",\"127.0.0.1\"]",
"default": [
"localhost",
"127.0.0.1"
]
},
"ga4.measurementId": {
"type": "string",
"description": "GA4 Measurement ID for Measurement Protocol.\n@default \"\"\n@environment `GA4_MEASUREMENT_ID`",
"default": ""
},
"ga4.apiSecret": {
"type": "string",
"description": "GA4 API secret for Measurement Protocol.\n@default \"\"\n@environment `GA4_API_SECRET`",
"default": ""
},
"dedupe.ttlHours": {
"type": "number",
"description": "Telemetry dedupe TTL in hours.\n@default 24",
"default": 24
},
"dedupe.maxEntries": {
"type": "number",
"description": "Telemetry dedupe max entries.\n@default 100000",
"default": 100000
},
"batch.maxEvents": {
"type": "number",
"description": "Max events per telemetry batch.\n@default 25",
"default": 25
}
}
},
"client": { "client": {
"type": "object", "type": "object",
"description": "Configuration for client module", "description": "Configuration for client module",
@@ -832,108 +585,8 @@
}, },
"versionControl.requiredVersion": { "versionControl.requiredVersion": {
"type": "string", "type": "string",
"description": "Allowed version range of the app that allowed to access the server. Requires 'client/versionControl.enabled' to be true to take effect.\n@default \">=0.25.0\"", "description": "Allowed version range of the app that allowed to access the server. Requires 'client/versionControl.enabled' to be true to take effect.\n@default \">=0.20.0\"",
"default": ">=0.25.0" "default": ">=0.20.0"
}
}
},
"calendar": {
"type": "object",
"description": "Configuration for calendar module",
"properties": {
"google": {
"type": "object",
"description": "Google Calendar integration config\n@default {\"enabled\":false,\"clientId\":\"\",\"clientSecret\":\"\",\"externalWebhookUrl\":\"\",\"webhookVerificationToken\":\"\"}\n@link https://developers.google.com/calendar/api/guides/push",
"properties": {
"enabled": {
"type": "boolean"
},
"clientId": {
"type": "string"
},
"clientSecret": {
"type": "string"
},
"externalWebhookUrl": {
"type": "string"
},
"webhookVerificationToken": {
"type": "string"
}
},
"default": {
"enabled": false,
"clientId": "",
"clientSecret": "",
"externalWebhookUrl": "",
"webhookVerificationToken": ""
}
},
"caldav": {
"type": "object",
"description": "CalDAV integration config\n@default {\"enabled\":false,\"allowCustomProvider\":false,\"providers\":[],\"allowInsecureHttp\":false,\"allowedHosts\":[],\"blockPrivateNetwork\":true,\"requestTimeoutMs\":10000,\"maxRedirects\":5}",
"properties": {
"enabled": {
"type": "boolean"
},
"allowCustomProvider": {
"type": "boolean"
},
"providers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"label": {
"type": "string"
},
"serverUrl": {
"type": "string"
},
"authType": {
"type": "string"
},
"requiresAppPassword": {
"type": "boolean"
},
"docsUrl": {
"type": "string"
}
}
}
},
"allowInsecureHttp": {
"type": "boolean"
},
"allowedHosts": {
"type": "array",
"items": {
"type": "string"
}
},
"blockPrivateNetwork": {
"type": "boolean"
},
"requestTimeoutMs": {
"type": "number"
},
"maxRedirects": {
"type": "number"
}
},
"default": {
"enabled": false,
"allowCustomProvider": false,
"providers": [],
"allowInsecureHttp": false,
"allowedHosts": [],
"blockPrivateNetwork": true,
"requestTimeoutMs": 10000,
"maxRedirects": 5
}
} }
} }
}, },
@@ -966,34 +619,14 @@
"properties": { "properties": {
"enabled": { "enabled": {
"type": "boolean", "type": "boolean",
"description": "Whether to enable the copilot plugin. <br> Document: <a href=\"https://docs.affine.pro/self-host-affine/administer/ai\" target=\"_blank\">https://docs.affine.pro/self-host-affine/administer/ai</a>\n@default false", "description": "Whether to enable the copilot plugin.\n@default false",
"default": false "default": false
}, },
"scenarios": {
"type": "object",
"description": "Use custom models in scenarios and override default settings.\n@default {\"override_enabled\":false,\"scenarios\":{\"audio_transcribing\":\"gemini-2.5-flash\",\"chat\":\"gemini-2.5-flash\",\"embedding\":\"gemini-embedding-001\",\"image\":\"gpt-image-1\",\"rerank\":\"gpt-4.1\",\"coding\":\"claude-sonnet-4-5@20250929\",\"complex_text_generation\":\"gpt-4o-2024-08-06\",\"quick_decision_making\":\"gpt-5-mini\",\"quick_text_generation\":\"gemini-2.5-flash\",\"polish_and_summarize\":\"gemini-2.5-flash\"}}",
"default": {
"override_enabled": false,
"scenarios": {
"audio_transcribing": "gemini-2.5-flash",
"chat": "gemini-2.5-flash",
"embedding": "gemini-embedding-001",
"image": "gpt-image-1",
"rerank": "gpt-4.1",
"coding": "claude-sonnet-4-5@20250929",
"complex_text_generation": "gpt-4o-2024-08-06",
"quick_decision_making": "gpt-5-mini",
"quick_text_generation": "gemini-2.5-flash",
"polish_and_summarize": "gemini-2.5-flash"
}
}
},
"providers.openai": { "providers.openai": {
"type": "object", "type": "object",
"description": "The config for the openai provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.openai.com/v1\"}\n@link https://github.com/openai/openai-node", "description": "The config for the openai provider.\n@default {\"apiKey\":\"\"}\n@link https://github.com/openai/openai-node",
"default": { "default": {
"apiKey": "", "apiKey": ""
"baseURL": "https://api.openai.com/v1"
} }
}, },
"providers.fal": { "providers.fal": {
@@ -1005,10 +638,9 @@
}, },
"providers.gemini": { "providers.gemini": {
"type": "object", "type": "object",
"description": "The config for the gemini provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://generativelanguage.googleapis.com/v1beta\"}", "description": "The config for the gemini provider.\n@default {\"apiKey\":\"\"}",
"default": { "default": {
"apiKey": "", "apiKey": ""
"baseURL": "https://generativelanguage.googleapis.com/v1beta"
} }
}, },
"providers.geminiVertex": { "providers.geminiVertex": {
@@ -1055,10 +687,9 @@
}, },
"providers.anthropic": { "providers.anthropic": {
"type": "object", "type": "object",
"description": "The config for the anthropic provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.anthropic.com/v1\"}", "description": "The config for the anthropic provider.\n@default {\"apiKey\":\"\"}",
"default": { "default": {
"apiKey": "", "apiKey": ""
"baseURL": "https://api.anthropic.com/v1"
} }
}, },
"providers.anthropicVertex": { "providers.anthropicVertex": {
@@ -1096,11 +727,6 @@
}, },
"default": {} "default": {}
}, },
"providers.morph": {
"type": "object",
"description": "The config for the morph provider.\n@default {}",
"default": {}
},
"unsplash": { "unsplash": {
"type": "object", "type": "object",
"description": "The config for the unsplash key.\n@default {\"key\":\"\"}", "description": "The config for the unsplash key.\n@default {\"key\":\"\"}",
@@ -1155,42 +781,8 @@
}, },
"config": { "config": {
"type": "object", "type": "object",
"description": "The config for the S3 compatible storage provider.", "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
"properties": { "properties": {
"endpoint": {
"type": "string",
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
},
"region": {
"type": "string",
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
},
"forcePathStyle": {
"type": "boolean",
"description": "Whether to use path-style bucket addressing."
},
"requestTimeoutMs": {
"type": "number",
"description": "Request timeout in milliseconds."
},
"minPartSize": {
"type": "number",
"description": "Minimum multipart part size in bytes."
},
"presign": {
"type": "object",
"description": "Presigned URL behavior configuration.",
"properties": {
"expiresInSeconds": {
"type": "number",
"description": "Expiration time in seconds for presigned URLs."
},
"signContentTypeForPut": {
"type": "boolean",
"description": "Whether to sign Content-Type for presigned PUT."
}
}
},
"credentials": { "credentials": {
"type": "object", "type": "object",
"description": "The credentials for the s3 compatible storage provider.", "description": "The credentials for the s3 compatible storage provider.",
@@ -1200,9 +792,6 @@
}, },
"secretAccessKey": { "secretAccessKey": {
"type": "string" "type": "string"
},
"sessionToken": {
"type": "string"
} }
} }
} }
@@ -1224,42 +813,8 @@
}, },
"config": { "config": {
"type": "object", "type": "object",
"description": "The config for the S3 compatible storage provider.", "description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
"properties": { "properties": {
"endpoint": {
"type": "string",
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
},
"region": {
"type": "string",
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
},
"forcePathStyle": {
"type": "boolean",
"description": "Whether to use path-style bucket addressing."
},
"requestTimeoutMs": {
"type": "number",
"description": "Request timeout in milliseconds."
},
"minPartSize": {
"type": "number",
"description": "Minimum multipart part size in bytes."
},
"presign": {
"type": "object",
"description": "Presigned URL behavior configuration.",
"properties": {
"expiresInSeconds": {
"type": "number",
"description": "Expiration time in seconds for presigned URLs."
},
"signContentTypeForPut": {
"type": "boolean",
"description": "Whether to sign Content-Type for presigned PUT."
}
}
},
"credentials": { "credentials": {
"type": "object", "type": "object",
"description": "The credentials for the s3 compatible storage provider.", "description": "The credentials for the s3 compatible storage provider.",
@@ -1269,9 +824,6 @@
}, },
"secretAccessKey": { "secretAccessKey": {
"type": "string" "type": "string"
},
"sessionToken": {
"type": "string"
} }
} }
}, },
@@ -1289,7 +841,7 @@
}, },
"urlPrefix": { "urlPrefix": {
"type": "string", "type": "string",
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)" "description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
}, },
"signKey": { "signKey": {
"type": "string", "type": "string",
@@ -1312,14 +864,30 @@
} }
} }
}, },
"customerIo": {
"type": "object",
"description": "Configuration for customerIo module",
"properties": {
"enabled": {
"type": "boolean",
"description": "Enable customer.io integration\n@default false",
"default": false
},
"token": {
"type": "string",
"description": "Customer.io token\n@default \"\"",
"default": ""
}
}
},
"indexer": { "indexer": {
"type": "object", "type": "object",
"description": "Configuration for indexer module", "description": "Configuration for indexer module",
"properties": { "properties": {
"enabled": { "enabled": {
"type": "boolean", "type": "boolean",
"description": "Enable indexer plugin\n@default false\n@environment `AFFINE_INDEXER_ENABLED`", "description": "Enable indexer plugin\n@default true\n@environment `AFFINE_INDEXER_ENABLED`",
"default": false "default": true
}, },
"provider.type": { "provider.type": {
"type": "string", "type": "string",
@@ -1353,22 +921,6 @@
} }
} }
}, },
"customerIo": {
"type": "object",
"description": "Configuration for customerIo module",
"properties": {
"enabled": {
"type": "boolean",
"description": "Enable customer.io integration\n@default false",
"default": false
},
"token": {
"type": "string",
"description": "Customer.io token\n@default \"\"",
"default": ""
}
}
},
"oauth": { "oauth": {
"type": "object", "type": "object",
"description": "Configuration for oauth module", "description": "Configuration for oauth module",
@@ -1469,33 +1021,18 @@
}, },
"apiKey": { "apiKey": {
"type": "string", "type": "string",
"description": "[Deprecated] Stripe API key. Use payment.stripe.apiKey instead.\n@default \"\"\n@environment `STRIPE_API_KEY`", "description": "Stripe API key to enable payment service.\n@default \"\"\n@environment `STRIPE_API_KEY`",
"default": "" "default": ""
}, },
"webhookKey": { "webhookKey": {
"type": "string", "type": "string",
"description": "[Deprecated] Stripe webhook key. Use payment.stripe.webhookKey instead.\n@default \"\"\n@environment `STRIPE_WEBHOOK_KEY`", "description": "Stripe webhook key to enable payment service.\n@default \"\"\n@environment `STRIPE_WEBHOOK_KEY`",
"default": "" "default": ""
}, },
"stripe": { "stripe": {
"type": "object", "type": "object",
"description": "Stripe sdk options and credentials\n@default {\"apiKey\":\"\",\"webhookKey\":\"\"}\n@link https://docs.stripe.com/api", "description": "Stripe sdk options\n@default {}\n@link https://docs.stripe.com/api",
"default": { "default": {}
"apiKey": "",
"webhookKey": ""
}
},
"revenuecat": {
"type": "object",
"description": "RevenueCat integration configs\n@default {\"enabled\":false,\"apiKey\":\"\",\"projectId\":\"\",\"webhookAuth\":\"\",\"environment\":\"production\",\"productMap\":{}}\n@link https://www.revenuecat.com/docs/",
"default": {
"enabled": false,
"apiKey": "",
"projectId": "",
"webhookAuth": "",
"environment": "production",
"productMap": {}
}
} }
} }
}, },

View File

@@ -1,26 +0,0 @@
.git
.github/**/*.md
.gitignore
# Local dependency/build artifacts
/node_modules
/target
# Yarn v4 artifacts (not needed for image packaging)
/.yarn/cache
/.yarn/unplugged
/.yarn/install-state.gz
/.pnp.*
# Test artifacts
/test-results
/playwright-report
/coverage
/.coverage
# OS noise
.DS_Store
# Sourcemaps (keep server sourcemap for backend stacktraces)
**/*.map
!packages/backend/server/dist/main.js.map

View File

@@ -1,8 +1,6 @@
# Editor configuration, see http://editorconfig.org # Editor configuration, see http://editorconfig.org
root = true root = true
[*.rs]
max_line_length = 120
[*] [*]
charset = utf-8 charset = utf-8
indent_style = space indent_style = space

View File

@@ -74,11 +74,3 @@ body:
description: | description: |
Links? References? Anything that will give us more context about the issue you are encountering! Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images here Tip: You can attach images here
- type: checkboxes
attributes:
label: Is your content generated by AI?
description: >
(Required) Please confirm that the content you submit was not generated by AI or only minimally edited by AI.
If an administrator believes the post contains a large amount of AI-generated content, they may directly close the question.
options:
- label: I confirm that the content I submitted was **not** generated by AI / **merely contained minimal** AI edits.

View File

@@ -2,6 +2,7 @@ name: Feature Request
description: Suggest a feature or improvement description: Suggest a feature or improvement
title: '[Feature Request]: ' title: '[Feature Request]: '
labels: ['feat', 'story'] labels: ['feat', 'story']
assignees: ['hwangdev97']
body: body:
- type: markdown - type: markdown
attributes: attributes:
@@ -34,11 +35,3 @@ body:
See the AFFiNE [Contributing Guide](https://github.com/toeverything/affine/blob/canary/CONTRIBUTING.md) to get started. See the AFFiNE [Contributing Guide](https://github.com/toeverything/affine/blob/canary/CONTRIBUTING.md) to get started.
options: options:
- label: Yes I'd like to help by submitting a PR! - label: Yes I'd like to help by submitting a PR!
- type: checkboxes
attributes:
label: Is your content generated by AI?
description: >
(Required) Please confirm that the content you submit was not generated by AI or only minimally edited by AI.
If an administrator believes the post contains a large amount of AI-generated content, they may directly close the question.
options:
- label: I confirm that the content I submitted was **not** generated by AI / **merely contained minimal** AI edits.

View File

@@ -75,11 +75,7 @@ runs:
shell: bash shell: bash
if: ${{ runner.os != 'Windows' && inputs.no-build != 'true' }} if: ${{ runner.os != 'Windows' && inputs.no-build != 'true' }}
run: | run: |
if [[ "${{ inputs.target }}" == "x86_64-unknown-linux-gnu" ]]; then yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }} --use-napi-cross
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }}
else
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }} --use-napi-cross
fi
env: env:
DEBUG: 'napi:*' DEBUG: 'napi:*'

View File

@@ -1,6 +1,9 @@
name: 'Deploy to Cluster' name: 'Deploy to Cluster'
description: 'Deploy AFFiNE Cloud to cluster' description: 'Deploy AFFiNE Cloud to cluster'
inputs: inputs:
build-type:
description: 'Align with App build type, canary|beta|stable|internal'
default: 'canary'
gcp-project-number: gcp-project-number:
description: 'GCP project number' description: 'GCP project number'
required: true required: true
@@ -33,3 +36,5 @@ runs:
- name: Deploy - name: Deploy
shell: bash shell: bash
run: node ./.github/actions/deploy/deploy.mjs run: node ./.github/actions/deploy/deploy.mjs
env:
BUILD_TYPE: '${{ inputs.build-type }}'

View File

@@ -25,30 +25,47 @@ const buildType = BUILD_TYPE || 'canary';
const isProduction = buildType === 'stable'; const isProduction = buildType === 'stable';
const isBeta = buildType === 'beta'; const isBeta = buildType === 'beta';
const isCanary = buildType === 'canary';
const isInternal = buildType === 'internal'; const isInternal = buildType === 'internal';
const isSpotEnabled = isBeta || isCanary;
const replicaConfig = { const replicaConfig = {
stable: { stable: {
front: Number(process.env.PRODUCTION_FRONT_REPLICA) || 2, web: 3,
graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 2, graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 3,
sync: Number(process.env.PRODUCTION_SYNC_REPLICA) || 3,
renderer: Number(process.env.PRODUCTION_RENDERER_REPLICA) || 3,
doc: Number(process.env.PRODUCTION_DOC_REPLICA) || 3,
}, },
beta: { beta: {
front: Number(process.env.BETA_FRONT_REPLICA) || 1, web: 2,
graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 1, graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 2,
sync: Number(process.env.BETA_SYNC_REPLICA) || 2,
renderer: Number(process.env.BETA_RENDERER_REPLICA) || 2,
doc: Number(process.env.BETA_DOC_REPLICA) || 2,
},
canary: {
web: 2,
graphql: 2,
sync: 2,
renderer: 2,
doc: 2,
}, },
canary: { front: 1, graphql: 1 },
}; };
const cpuConfig = { const cpuConfig = {
beta: { front: '1', graphql: '1' }, beta: {
canary: { front: '500m', graphql: '1' }, web: '300m',
}; graphql: '1',
sync: '1',
const memoryConfig = { doc: '1',
beta: { front: '2Gi', graphql: '1Gi' }, renderer: '300m',
canary: { front: '512Mi', graphql: '512Mi' }, },
canary: {
web: '300m',
graphql: '1',
sync: '1',
doc: '1',
renderer: '300m',
},
}; };
const createHelmCommand = ({ isDryRun }) => { const createHelmCommand = ({ isDryRun }) => {
@@ -72,47 +89,32 @@ const createHelmCommand = ({ isDryRun }) => {
`--set-string global.indexer.endpoint="${AFFINE_INDEXER_SEARCH_ENDPOINT}"`, `--set-string global.indexer.endpoint="${AFFINE_INDEXER_SEARCH_ENDPOINT}"`,
`--set-string global.indexer.apiKey="${AFFINE_INDEXER_SEARCH_API_KEY}"`, `--set-string global.indexer.apiKey="${AFFINE_INDEXER_SEARCH_API_KEY}"`,
]; ];
const cloudSqlNodeSelector = isBeta
? `{ \\"iam.gke.io/gke-metadata-server-enabled\\": \\"true\\", \\"cloud.google.com/gke-spot\\": \\"true\\" }`
: `{ \\"iam.gke.io/gke-metadata-server-enabled\\": \\"true\\" }`;
const serviceAnnotations = [ const serviceAnnotations = [
`--set-json front.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`, `--set-json web.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
`--set-json graphql.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`, `--set-json graphql.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
`--set-json sync.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
`--set-json doc.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
].concat( ].concat(
isProduction || isBeta || isInternal isProduction || isBeta || isInternal
? [ ? [
`--set-json front.services.web.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`, `--set-json web.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
`--set-json front.services.sync.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
`--set-json front.services.renderer.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
`--set-json graphql.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`, `--set-json graphql.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
`--set-json sync.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
`--set-json cloud-sql-proxy.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${CLOUD_SQL_IAM_ACCOUNT}\\" }"`, `--set-json cloud-sql-proxy.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${CLOUD_SQL_IAM_ACCOUNT}\\" }"`,
`--set-json cloud-sql-proxy.nodeSelector="${cloudSqlNodeSelector}"`, `--set-json cloud-sql-proxy.nodeSelector="{ \\"iam.gke.io/gke-metadata-server-enabled\\": \\"true\\" }"`,
] ]
: [] : []
); );
const spotNodeSelector = `{ \\"cloud.google.com/gke-spot\\": \\"true\\" }`;
const spotScheduling = isSpotEnabled
? [
`--set-json front.nodeSelector="${spotNodeSelector}"`,
`--set-json graphql.nodeSelector="${spotNodeSelector}"`,
]
: [];
const cpu = cpuConfig[buildType]; const cpu = cpuConfig[buildType];
const memory = memoryConfig[buildType]; const resources = cpu
let resources = []; ? [
if (cpu) { `--set web.resources.requests.cpu="${cpu.web}"`,
resources = resources.concat([ `--set graphql.resources.requests.cpu="${cpu.graphql}"`,
`--set front.resources.requests.cpu="${cpu.front}"`, `--set sync.resources.requests.cpu="${cpu.sync}"`,
`--set graphql.resources.requests.cpu="${cpu.graphql}"`, `--set doc.resources.requests.cpu="${cpu.doc}"`,
]); ]
} : [];
if (memory) {
resources = resources.concat([
`--set front.resources.requests.memory="${memory.front}"`,
`--set graphql.resources.requests.memory="${memory.graphql}"`,
]);
}
const replica = replicaConfig[buildType] || replicaConfig.canary; const replica = replicaConfig[buildType] || replicaConfig.canary;
@@ -124,11 +126,7 @@ const createHelmCommand = ({ isDryRun }) => {
? 'internal' ? 'internal'
: 'dev'; : 'dev';
const hosts = (DEPLOY_HOST || CANARY_DEPLOY_HOST) const host = DEPLOY_HOST || CANARY_DEPLOY_HOST;
.split(',')
.map(host => host.trim())
.filter(host => host);
const primaryHost = hosts[0] || '0.0.0.0';
const deployCommand = [ const deployCommand = [
`helm upgrade --install affine .github/helm/affine`, `helm upgrade --install affine .github/helm/affine`,
`--namespace ${namespace}`, `--namespace ${namespace}`,
@@ -137,20 +135,24 @@ const createHelmCommand = ({ isDryRun }) => {
`--set-string global.app.buildType="${buildType}"`, `--set-string global.app.buildType="${buildType}"`,
`--set global.ingress.enabled=true`, `--set global.ingress.enabled=true`,
`--set-json global.ingress.annotations="{ \\"kubernetes.io/ingress.class\\": \\"gce\\", \\"kubernetes.io/ingress.allow-http\\": \\"true\\", \\"kubernetes.io/ingress.global-static-ip-name\\": \\"${STATIC_IP_NAME}\\" }"`, `--set-json global.ingress.annotations="{ \\"kubernetes.io/ingress.class\\": \\"gce\\", \\"kubernetes.io/ingress.allow-http\\": \\"true\\", \\"kubernetes.io/ingress.global-static-ip-name\\": \\"${STATIC_IP_NAME}\\" }"`,
...hosts.map( `--set-string global.ingress.host="${host}"`,
(host, index) => `--set global.ingress.hosts[${index}]=${host}`
),
`--set-string global.version="${APP_VERSION}"`, `--set-string global.version="${APP_VERSION}"`,
...redisAndPostgres, ...redisAndPostgres,
...indexerOptions, ...indexerOptions,
`--set front.replicaCount=${replica.front}`, `--set web.replicaCount=${replica.web}`,
`--set-string front.image.tag="${imageTag}"`, `--set-string web.image.tag="${imageTag}"`,
`--set-string front.app.host="${primaryHost}"`,
`--set graphql.replicaCount=${replica.graphql}`, `--set graphql.replicaCount=${replica.graphql}`,
`--set-string graphql.image.tag="${imageTag}"`, `--set-string graphql.image.tag="${imageTag}"`,
`--set-string graphql.app.host="${primaryHost}"`, `--set graphql.app.host=${host}`,
`--set sync.replicaCount=${replica.sync}`,
`--set-string sync.image.tag="${imageTag}"`,
`--set-string renderer.image.tag="${imageTag}"`,
`--set renderer.app.host=${host}`,
`--set renderer.replicaCount=${replica.renderer}`,
`--set-string doc.image.tag="${imageTag}"`,
`--set doc.app.host=${host}`,
`--set doc.replicaCount=${replica.doc}`,
...serviceAnnotations, ...serviceAnnotations,
...spotScheduling,
...resources, ...resources,
`--timeout 10m`, `--timeout 10m`,
flag, flag,

View File

@@ -1,41 +0,0 @@
name: Prepare Release
description: 'Prepare Release'
outputs:
APP_VERSION:
description: 'App Version'
value: ${{ steps.get-version.outputs.APP_VERSION }}
GIT_SHORT_HASH:
description: 'Git Short Hash'
value: ${{ steps.get-version.outputs.GIT_SHORT_HASH }}
BUILD_TYPE:
description: 'Build Type'
value: ${{ steps.get-version.outputs.BUILD_TYPE }}
runs:
using: 'composite'
steps:
- name: Get Version
id: get-version
shell: bash
run: |
GIT_SHORT_HASH=$(git rev-parse --short HEAD)
if [ "${{ github.ref_type }}" == "tag" ]; then
APP_VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//')
else
APP_VERSION=$(date '+%Y.%-m.%-d-canary.%-H%M')
fi
if [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
BUILD_TYPE=stable
elif [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$ ]]; then
BUILD_TYPE=beta
elif [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-canary\.[0-9a-f]+$ ]]; then
BUILD_TYPE=canary
else
echo "Error: unsupported version string: $APP_VERSION" >&2
exit 1
fi
echo $APP_VERSION
echo $GIT_SHORT_HASH
echo $BUILD_TYPE
echo "APP_VERSION=$APP_VERSION" >> "$GITHUB_OUTPUT"
echo "GIT_SHORT_HASH=$GIT_SHORT_HASH" >> "$GITHUB_OUTPUT"
echo "BUILD_TYPE=$BUILD_TYPE" >> "$GITHUB_OUTPUT"

View File

@@ -4,6 +4,11 @@ description: 'Prepare Server Test Environment'
runs: runs:
using: 'composite' using: 'composite'
steps: steps:
- name: Bundle @affine/reader
shell: bash
run: |
yarn affine @affine/reader build
- name: Initialize database - name: Initialize database
shell: bash shell: bash
run: | run: |

View File

@@ -1,18 +1,24 @@
name: Setup Version name: Setup Version
description: 'Setup Version' description: 'Setup Version'
inputs: outputs:
app-version: APP_VERSION:
description: 'App Version' description: 'App Version'
required: true value: ${{ steps.version.outputs.APP_VERSION }}
ios-app-version:
description: 'iOS App Store Version (Optional, use App version if empty)'
required: false
type: string
runs: runs:
using: 'composite' using: 'composite'
steps: steps:
- name: 'Write Version' - name: 'Write Version'
id: version
shell: bash shell: bash
env: run: |
IOS_APP_VERSION: ${{ inputs.ios-app-version }} if [ "${{ github.ref_type }}" == "tag" ]; then
run: ./scripts/set-version.sh ${{ inputs.app-version }} APP_VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//')
else
PACKAGE_VERSION=$(node -p "require('./package.json').version")
TIME_VERSION=$(date +%Y%m%d%H%M)
GIT_SHORT_HASH=$(git rev-parse --short HEAD)
APP_VERSION=$PACKAGE_VERSION-nightly-$GIT_SHORT_HASH
fi
echo $APP_VERSION
echo "APP_VERSION=$APP_VERSION" >> "$GITHUB_OUTPUT"
./scripts/set-version.sh $APP_VERSION

13
.github/deployment/front/Dockerfile vendored Normal file
View File

@@ -0,0 +1,13 @@
FROM openresty/openresty:1.27.1.1-0-buster
WORKDIR /app
COPY ./packages/frontend/apps/web/dist ./dist
COPY ./packages/frontend/admin/dist ./admin
COPY ./packages/frontend/apps/mobile/dist ./mobile
COPY ./.github/deployment/front/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
COPY ./.github/deployment/front/affine.nginx.conf /etc/nginx/conf.d/affine.nginx.conf
RUN mkdir -p /var/log/nginx && \
rm /etc/nginx/conf.d/default.conf
EXPOSE 8080
CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]

View File

@@ -0,0 +1,42 @@
server {
listen 8080;
location /admin {
root /app/;
index index.html;
try_files $uri/index.html $uri/ $uri /admin/index.html;
}
set $app_root_path /app/dist/;
set $mobile_root /app/dist/;
set_by_lua $affine_env 'return os.getenv("AFFINE_ENV")';
if ($affine_env = "dev") {
set $mobile_root /app/mobile/;
}
# https://gist.github.com/mariusom/6683dc52b1cad1a1f372e908bdb209d0
if ($http_user_agent ~* "(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino") {
set $app_root_path $mobile_root;
}
if ($http_user_agent ~* "^(1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-)") {
set $app_root_path $mobile_root;
}
location ~ ^/(_plugin|assets|imgs|js|plugins|static)/ {
root $app_root_path;
try_files $uri $uri/ =404;
}
location / {
root $app_root_path;
index index.html;
try_files $uri $uri/ /index.html;
add_header Cache-Control "private, no-cache, no-store, max-age=0, must-revalidate";
}
error_page 404 /404.html;
location = /404.html {
internal;
}
}

15
.github/deployment/front/nginx.conf vendored Normal file
View File

@@ -0,0 +1,15 @@
worker_processes 4;
error_log /var/log/nginx/error.log warn;
pcre_jit on;
env AFFINE_ENV;
events {
worker_connections 1024;
}
http {
include mime.types;
log_format main '$remote_addr [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
include /etc/nginx/conf.d/*.conf;
}

View File

@@ -1,35 +1,13 @@
# syntax=docker/dockerfile:1.7 FROM node:22-bookworm-slim
FROM node:22-bookworm-slim AS assets
WORKDIR /app
COPY ./packages/backend/server /app COPY ./packages/backend/server /app
COPY ./packages/frontend/apps/web/dist /app/static COPY ./packages/frontend/apps/web/dist /app/static
COPY ./packages/frontend/admin/dist /app/static/admin COPY ./packages/frontend/admin/dist /app/static/admin
COPY ./packages/frontend/apps/mobile/dist /app/static/mobile COPY ./packages/frontend/apps/mobile/dist /app/static/mobile
# Keep server sourcemap for stacktraces, but don't ship frontend/node_modules sourcemaps.
ARG TARGETARCH
ARG TARGETVARIANT
# Needed for Prisma engine resolution (and potential engine download during cleanup).
RUN apt-get update && \
apt-get install -y --no-install-recommends openssl ca-certificates && \
rm -rf /var/lib/apt/lists/*
RUN AFFINE_DOCKER_CLEAN=1 TARGETARCH="${TARGETARCH}" TARGETVARIANT="${TARGETVARIANT}" node ./scripts/docker-clean.mjs
FROM node:22-bookworm-slim
WORKDIR /app WORKDIR /app
COPY --from=assets /app /app
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends openssl libjemalloc2 && \ apt-get install -y --no-install-recommends openssl && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# Enable jemalloc by preloading the library
ENV LD_PRELOAD=libjemalloc.so.2
EXPOSE 3010
CMD ["node", "./dist/main.js"] CMD ["node", "./dist/main.js"]

View File

@@ -3,4 +3,4 @@ name: affine
description: AFFiNE cloud chart description: AFFiNE cloud chart
type: application type: application
version: 0.0.0 version: 0.0.0
appVersion: "0.26.1" appVersion: "0.21.0"

View File

@@ -3,7 +3,7 @@ name: doc
description: AFFiNE doc server description: AFFiNE doc server
type: application type: application
version: 0.0.0 version: 0.0.0
appVersion: "0.26.1" appVersion: "0.20.0"
dependencies: dependencies:
- name: gcloud-sql-proxy - name: gcloud-sql-proxy
version: 0.0.0 version: 0.0.0

View File

@@ -1,15 +1,15 @@
1. Get the application URL by running these commands: 1. Get the application URL by running these commands:
{{- if contains "NodePort" .Values.services.sync.type }} {{- if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ .Values.services.sync.name }}) export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "doc.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.services.sync.type }} {{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available. NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ .Values.services.sync.name }}' You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "doc.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ .Values.services.sync.name }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "doc.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.services.sync.port }} echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.services.sync.type }} {{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "front.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "doc.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application" echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT

View File

@@ -0,0 +1,63 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "doc.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "doc.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "doc.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "doc.labels" -}}
helm.sh/chart: {{ include "doc.chart" . }}
{{ include "doc.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
monitoring: enabled
{{- end }}
{{/*
Selector labels
*/}}
{{- define "doc.selectorLabels" -}}
app.kubernetes.io/name: {{ include "doc.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "doc.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "doc.fullname" .) .Values.global.docService.name }}
{{- else }}
{{- default "default" .Values.global.docService.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,116 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "doc.fullname" . }}
labels:
{{- include "doc.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "doc.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "doc.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "doc.serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: AFFINE_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.global.secret.secretName }}"
key: key
- name: NODE_ENV
value: "{{ .Values.env }}"
- name: NODE_OPTIONS
value: "--max-old-space-size=4096"
- name: NO_COLOR
value: "1"
- name: DEPLOYMENT_TYPE
value: "{{ .Values.global.deployment.type }}"
- name: DEPLOYMENT_PLATFORM
value: "{{ .Values.global.deployment.platform }}"
- name: SERVER_FLAVOR
value: "doc"
- name: AFFINE_ENV
value: "{{ .Release.Namespace }}"
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: pg-postgresql
key: postgres-password
- name: DATABASE_URL
value: postgres://{{ .Values.global.database.user }}:$(DATABASE_PASSWORD)@{{ .Values.global.database.host }}:{{ .Values.global.database.port }}/{{ .Values.global.database.name }}
- name: REDIS_SERVER_ENABLED
value: "true"
- name: REDIS_SERVER_HOST
value: "{{ .Values.global.redis.host }}"
- name: REDIS_SERVER_PORT
value: "{{ .Values.global.redis.port }}"
- name: REDIS_SERVER_USER
value: "{{ .Values.global.redis.username }}"
- name: REDIS_SERVER_PASSWORD
valueFrom:
secretKeyRef:
name: redis
key: redis-password
- name: REDIS_SERVER_DATABASE
value: "{{ .Values.global.redis.database }}"
- name: AFFINE_INDEXER_SEARCH_PROVIDER
value: "{{ .Values.global.indexer.provider }}"
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
value: "{{ .Values.global.indexer.endpoint }}"
- name: AFFINE_INDEXER_SEARCH_API_KEY
valueFrom:
secretKeyRef:
name: indexer
key: indexer-apiKey
- name: AFFINE_SERVER_PORT
value: "{{ .Values.global.docService.port }}"
- name: AFFINE_SERVER_SUB_PATH
value: "{{ .Values.app.path }}"
- name: AFFINE_SERVER_HOST
value: "{{ .Values.app.host }}"
- name: AFFINE_SERVER_HTTPS
value: "{{ .Values.app.https }}"
ports:
- name: http
containerPort: {{ .Values.global.docService.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
readinessProbe:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -1,19 +1,19 @@
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: {{ .Values.global.docService.name }} name: {{ include "doc.fullname" . }}
labels: labels:
{{- include "front.labels" . | nindent 4 }} {{- include "doc.labels" . | nindent 4 }}
{{- with .Values.services.doc.annotations }} {{- with .Values.service.annotations }}
annotations: annotations:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
{{- end }} {{- end }}
spec: spec:
type: {{ .Values.services.doc.type }} type: {{ .Values.service.type }}
ports: ports:
- port: {{ .Values.global.docService.port }} - port: {{ .Values.global.docService.port }}
targetPort: http targetPort: http
protocol: TCP protocol: TCP
name: http name: http
selector: selector:
{{- include "front.selectorLabels" . | nindent 4 }} {{- include "doc.selectorLabels" . | nindent 4 }}

View File

@@ -2,9 +2,9 @@
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: {{ include "front.serviceAccountName" . }} name: {{ include "doc.serviceAccountName" . }}
labels: labels:
{{- include "front.labels" . | nindent 4 }} {{- include "doc.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }} {{- with .Values.serviceAccount.annotations }}
annotations: annotations:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}

View File

@@ -1,9 +1,9 @@
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: "{{ include "front.fullname" . }}-test-connection" name: "{{ include "doc.fullname" . }}-test-connection"
labels: labels:
{{- include "front.labels" . | nindent 4 }} {{- include "doc.labels" . | nindent 4 }}
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
spec: spec:
@@ -11,5 +11,5 @@ spec:
- name: wget - name: wget
image: busybox image: busybox
command: ['wget'] command: ['wget']
args: ['{{ .Values.services.sync.name }}:{{ .Values.services.sync.port }}'] args: ['{{ include "doc.fullname" . }}:{{ .Values.global.docService.port }}']
restartPolicy: Never restartPolicy: Never

View File

@@ -1,6 +1,6 @@
replicaCount: 1 replicaCount: 1
image: image:
repository: ghcr.io/toeverything/affine repository: ghcr.io/toeverything/affine-graphql
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
tag: '' tag: ''
@@ -30,16 +30,12 @@ podSecurityContext:
fsGroup: 2000 fsGroup: 2000
resources: resources:
limits:
cpu: '1'
memory: 4Gi
requests: requests:
cpu: '1' cpu: '1'
memory: 2Gi memory: 4Gi
probe: probe:
initialDelaySeconds: 20 initialDelaySeconds: 20
timeoutSeconds: 5
nodeSelector: {} nodeSelector: {}
tolerations: [] tolerations: []

View File

@@ -1,118 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "front.fullname" . }}
labels:
{{- include "front.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "front.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "front.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "front.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: AFFINE_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.global.secret.secretName }}"
key: key
- name: NODE_ENV
value: "{{ .Values.env }}"
- name: NODE_OPTIONS
value: "{{ .Values.nodeOptions }}"
- name: NO_COLOR
value: "1"
- name: DEPLOYMENT_TYPE
value: "{{ .Values.global.deployment.type }}"
- name: DEPLOYMENT_PLATFORM
value: "{{ .Values.global.deployment.platform }}"
- name: SERVER_FLAVOR
value: "front"
- name: AFFINE_ENV
value: "{{ .Release.Namespace }}"
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: pg-postgresql
key: postgres-password
- name: DATABASE_URL
value: postgres://{{ .Values.global.database.user }}:$(DATABASE_PASSWORD)@{{ .Values.global.database.host }}:{{ .Values.global.database.port }}/{{ .Values.global.database.name }}
- name: REDIS_SERVER_ENABLED
value: "true"
- name: REDIS_SERVER_HOST
value: "{{ .Values.global.redis.host }}"
- name: REDIS_SERVER_PORT
value: "{{ .Values.global.redis.port }}"
- name: REDIS_SERVER_USER
value: "{{ .Values.global.redis.username }}"
- name: REDIS_SERVER_PASSWORD
valueFrom:
secretKeyRef:
name: redis
key: redis-password
- name: REDIS_SERVER_DATABASE
value: "{{ .Values.global.redis.database }}"
- name: AFFINE_INDEXER_SEARCH_PROVIDER
value: "{{ .Values.global.indexer.provider }}"
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
value: "{{ .Values.global.indexer.endpoint }}"
- name: AFFINE_INDEXER_SEARCH_API_KEY
valueFrom:
secretKeyRef:
name: indexer
key: indexer-apiKey
- name: AFFINE_SERVER_PORT
value: "{{ .Values.app.port }}"
- name: AFFINE_SERVER_SUB_PATH
value: "{{ .Values.app.path }}"
- name: AFFINE_SERVER_HOST
value: "{{ .Values.app.host }}"
- name: AFFINE_SERVER_HTTPS
value: "{{ .Values.app.https }}"
ports:
- name: http
containerPort: {{ .Values.app.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
readinessProbe:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -1,19 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.services.renderer.name }}
labels:
{{- include "front.labels" . | nindent 4 }}
{{- with .Values.services.renderer.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.services.renderer.type }}
ports:
- port: {{ .Values.services.renderer.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "front.selectorLabels" . | nindent 4 }}

View File

@@ -1,19 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.services.sync.name }}
labels:
{{- include "front.labels" . | nindent 4 }}
{{- with .Values.services.sync.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.services.sync.type }}
ports:
- port: {{ .Values.services.sync.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "front.selectorLabels" . | nindent 4 }}

View File

@@ -1,19 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.services.web.name }}
labels:
{{- include "front.labels" . | nindent 4 }}
{{- with .Values.services.web.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.services.web.type }}
ports:
- port: {{ .Values.services.web.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "front.selectorLabels" . | nindent 4 }}

View File

@@ -1,66 +0,0 @@
replicaCount: 1
image:
repository: ghcr.io/toeverything/affine
pullPolicy: IfNotPresent
tag: ''
imagePullSecrets: []
nameOverride: ''
fullnameOverride: ''
# map to NODE_ENV environment variable
env: 'production'
nodeOptions: '--max-old-space-size=3072'
app:
# AFFINE_SERVER_PORT
port: 3010
# AFFINE_SERVER_SUB_PATH
path: ''
# AFFINE_SERVER_HOST
host: '0.0.0.0'
https: true
serviceAccount:
create: true
annotations: {}
name: 'affine-front'
podAnnotations: {}
podSecurityContext:
fsGroup: 2000
resources:
limits:
cpu: '1'
memory: 2Gi
requests:
cpu: '1'
memory: 2Gi
probe:
initialDelaySeconds: 20
services:
sync:
name: affine-sync
type: ClusterIP
port: 3010
annotations:
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
renderer:
name: affine-renderer
type: ClusterIP
port: 3000
annotations:
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
web:
name: affine-web
type: ClusterIP
port: 8080
annotations: {}
doc:
type: ClusterIP
annotations: {}
nodeSelector: {}
tolerations: []
affinity: {}

View File

@@ -1,4 +1,4 @@
replicaCount: 2 replicaCount: 3
enabled: false enabled: false
database: database:
connectionName: "" connectionName: ""
@@ -33,11 +33,8 @@ service:
resources: resources:
limits: limits:
memory: "1Gi" memory: "4Gi"
cpu: "1" cpu: "2"
requests:
memory: "512Mi"
cpu: "100m"
volumes: [] volumes: []
volumeMounts: [] volumeMounts: []

View File

@@ -3,7 +3,7 @@ name: graphql
description: AFFiNE GraphQL server description: AFFiNE GraphQL server
type: application type: application
version: 0.0.0 version: 0.0.0
appVersion: "0.26.1" appVersion: "0.21.0"
dependencies: dependencies:
- name: gcloud-sql-proxy - name: gcloud-sql-proxy
version: 0.0.0 version: 0.0.0

View File

@@ -1,6 +1,6 @@
replicaCount: 1 replicaCount: 1
image: image:
repository: ghcr.io/toeverything/affine repository: ghcr.io/toeverything/affine-graphql
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
tag: '' tag: ''
@@ -27,11 +27,8 @@ podSecurityContext:
fsGroup: 2000 fsGroup: 2000
resources: resources:
limits:
cpu: '1'
memory: 4Gi
requests: requests:
cpu: '1' cpu: '2'
memory: 2Gi memory: 2Gi
probe: probe:

View File

@@ -0,0 +1,11 @@
apiVersion: v2
name: renderer
description: AFFiNE renderer server
type: application
version: 0.0.0
appVersion: "0.16.0"
dependencies:
- name: gcloud-sql-proxy
version: 0.0.0
repository: "file://../gcloud-sql-proxy"
condition: .global.database.gcloud.enabled

View File

@@ -0,0 +1,16 @@
1. Get the application URL by running these commands:
{{- if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "renderer.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "renderer.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "renderer.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "renderer.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@@ -0,0 +1,63 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "renderer.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "renderer.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "renderer.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "renderer.labels" -}}
helm.sh/chart: {{ include "renderer.chart" . }}
{{ include "renderer.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
monitoring: enabled
{{- end }}
{{/*
Selector labels
*/}}
{{- define "renderer.selectorLabels" -}}
app.kubernetes.io/name: {{ include "renderer.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "renderer.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "renderer.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,118 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "renderer.fullname" . }}
labels:
{{- include "renderer.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "renderer.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "renderer.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "renderer.serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: AFFINE_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.global.secret.secretName }}"
key: key
- name: NODE_ENV
value: "{{ .Values.env }}"
- name: NODE_OPTIONS
value: "--max-old-space-size=2048"
- name: NO_COLOR
value: "1"
- name: DEPLOYMENT_TYPE
value: "{{ .Values.global.deployment.type }}"
- name: DEPLOYMENT_PLATFORM
value: "{{ .Values.global.deployment.platform }}"
- name: SERVER_FLAVOR
value: "renderer"
- name: AFFINE_ENV
value: "{{ .Release.Namespace }}"
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: pg-postgresql
key: postgres-password
- name: DATABASE_URL
value: postgres://{{ .Values.global.database.user }}:$(DATABASE_PASSWORD)@{{ .Values.global.database.host }}:{{ .Values.global.database.port }}/{{ .Values.global.database.name }}
- name: REDIS_SERVER_ENABLED
value: "true"
- name: REDIS_SERVER_HOST
value: "{{ .Values.global.redis.host }}"
- name: REDIS_SERVER_PORT
value: "{{ .Values.global.redis.port }}"
- name: REDIS_SERVER_USER
value: "{{ .Values.global.redis.username }}"
- name: REDIS_SERVER_PASSWORD
valueFrom:
secretKeyRef:
name: redis
key: redis-password
- name: REDIS_SERVER_DATABASE
value: "{{ .Values.global.redis.database }}"
- name: AFFINE_INDEXER_SEARCH_PROVIDER
value: "{{ .Values.global.indexer.provider }}"
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
value: "{{ .Values.global.indexer.endpoint }}"
- name: AFFINE_INDEXER_SEARCH_API_KEY
valueFrom:
secretKeyRef:
name: indexer
key: indexer-apiKey
- name: AFFINE_SERVER_PORT
value: "{{ .Values.service.port }}"
- name: AFFINE_SERVER_SUB_PATH
value: "{{ .Values.app.path }}"
- name: AFFINE_SERVER_HOST
value: "{{ .Values.app.host }}"
- name: AFFINE_SERVER_HTTPS
value: "{{ .Values.app.https }}"
- name: DOC_SERVICE_ENDPOINT
value: "http://{{ .Values.global.docService.name }}:{{ .Values.global.docService.port }}"
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
readinessProbe:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "graphql.fullname" . }}
labels:
{{- include "graphql.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "graphql.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "graphql.serviceAccountName" . }}
labels:
{{- include "graphql.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "renderer.fullname" . }}-test-connection"
labels:
{{- include "renderer.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "renderer.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

View File

@@ -0,0 +1,38 @@
replicaCount: 1
image:
repository: ghcr.io/toeverything/affine-graphql
pullPolicy: IfNotPresent
tag: ''
imagePullSecrets: []
nameOverride: ''
fullnameOverride: ''
# map to NODE_ENV environment variable
env: 'production'
app:
# AFFINE_SERVER_SUB_PATH
path: ''
# AFFINE_SERVER_HOST
host: '0.0.0.0'
https: true
serviceAccount:
create: true
annotations: {}
name: 'affine-renderer'
podAnnotations: {}
podSecurityContext:
fsGroup: 2000
resources:
requests:
cpu: '1'
memory: 2Gi
probe:
initialDelaySeconds: 20
nodeSelector: {}
tolerations: []
affinity: {}

View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -1,9 +1,9 @@
apiVersion: v2 apiVersion: v2
name: front name: sync
description: AFFiNE front server description: AFFiNE Sync Server
type: application type: application
version: 0.0.0 version: 0.0.0
appVersion: "0.26.1" appVersion: "0.21.0"
dependencies: dependencies:
- name: gcloud-sql-proxy - name: gcloud-sql-proxy
version: 0.0.0 version: 0.0.0

View File

@@ -0,0 +1,16 @@
1. Get the application URL by running these commands:
{{- if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "sync.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "sync.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "sync.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "sync.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@@ -1,7 +1,7 @@
{{/* {{/*
Expand the name of the chart. Expand the name of the chart.
*/}} */}}
{{- define "front.name" -}} {{- define "sync.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }} {{- end }}
@@ -10,7 +10,7 @@ Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name. If release name contains chart name it will be used as a full name.
*/}} */}}
{{- define "front.fullname" -}} {{- define "sync.fullname" -}}
{{- if .Values.fullnameOverride }} {{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }} {{- else }}
@@ -26,16 +26,16 @@ If release name contains chart name it will be used as a full name.
{{/* {{/*
Create chart name and version as used by the chart label. Create chart name and version as used by the chart label.
*/}} */}}
{{- define "front.chart" -}} {{- define "sync.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }} {{- end }}
{{/* {{/*
Common labels Common labels
*/}} */}}
{{- define "front.labels" -}} {{- define "sync.labels" -}}
helm.sh/chart: {{ include "front.chart" . }} helm.sh/chart: {{ include "sync.chart" . }}
{{ include "front.selectorLabels" . }} {{ include "sync.selectorLabels" . }}
{{- if .Chart.AppVersion }} {{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }} {{- end }}
@@ -46,17 +46,17 @@ monitoring: enabled
{{/* {{/*
Selector labels Selector labels
*/}} */}}
{{- define "front.selectorLabels" -}} {{- define "sync.selectorLabels" -}}
app.kubernetes.io/name: {{ include "front.name" . }} app.kubernetes.io/name: {{ include "sync.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }} {{- end }}
{{/* {{/*
Create the name of the service account to use Create the name of the service account to use
*/}} */}}
{{- define "front.serviceAccountName" -}} {{- define "sync.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }} {{- if .Values.serviceAccount.create }}
{{- default (include "front.fullname" .) .Values.serviceAccount.name }} {{- default (include "sync.fullname" .) .Values.serviceAccount.name }}
{{- else }} {{- else }}
{{- default "default" .Values.serviceAccount.name }} {{- default "default" .Values.serviceAccount.name }}
{{- end }} {{- end }}

View File

@@ -0,0 +1,112 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "sync.fullname" . }}
labels:
{{- include "sync.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "sync.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "sync.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sync.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: AFFINE_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.global.secret.secretName }}"
key: key
- name: NODE_ENV
value: "{{ .Values.env }}"
- name: NO_COLOR
value: "1"
- name: DEPLOYMENT_TYPE
value: "{{ .Values.global.deployment.type }}"
- name: DEPLOYMENT_PLATFORM
value: "{{ .Values.global.deployment.platform }}"
- name: SERVER_FLAVOR
value: "sync"
- name: AFFINE_ENV
value: "{{ .Release.Namespace }}"
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: pg-postgresql
key: postgres-password
- name: DATABASE_URL
value: postgres://{{ .Values.global.database.user }}:$(DATABASE_PASSWORD)@{{ .Values.global.database.host }}:{{ .Values.global.database.port }}/{{ .Values.global.database.name }}
- name: REDIS_SERVER_HOST
value: "{{ .Values.global.redis.host }}"
- name: REDIS_SERVER_PORT
value: "{{ .Values.global.redis.port }}"
- name: REDIS_SERVER_USER
value: "{{ .Values.global.redis.username }}"
- name: REDIS_SERVER_PASSWORD
valueFrom:
secretKeyRef:
name: redis
key: redis-password
- name: REDIS_SERVER_DATABASE
value: "{{ .Values.global.redis.database }}"
- name: AFFINE_INDEXER_SEARCH_PROVIDER
value: "{{ .Values.global.indexer.provider }}"
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
value: "{{ .Values.global.indexer.endpoint }}"
- name: AFFINE_INDEXER_SEARCH_API_KEY
valueFrom:
secretKeyRef:
name: indexer
key: indexer-apiKey
- name: AFFINE_SERVER_PORT
value: "{{ .Values.service.port }}"
- name: AFFINE_SERVER_HOST
value: "{{ .Values.app.host }}"
- name: DOC_SERVICE_ENDPOINT
value: "http://{{ .Values.global.docService.name }}:{{ .Values.global.docService.port }}"
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "sync.fullname" . }}
labels:
{{- include "sync.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "sync.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "sync.serviceAccountName" . }}
labels:
{{- include "sync.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "sync.fullname" . }}-test-connection"
labels:
{{- include "sync.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "sync.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

View File

@@ -0,0 +1,38 @@
replicaCount: 1
image:
repository: ghcr.io/toeverything/affine-graphql
pullPolicy: IfNotPresent
tag: ''
imagePullSecrets: []
nameOverride: ''
fullnameOverride: ''
# map to NODE_ENV environment variable
env: 'production'
app:
# AFFINE_SERVER_HOST
host: '0.0.0.0'
serviceAccount:
create: true
annotations: {}
name: 'affine-sync'
podAnnotations: {}
podSecurityContext:
fsGroup: 2000
resources:
limits:
cpu: '2'
memory: 4Gi
requests:
cpu: '1'
memory: 2Gi
probe:
initialDelaySeconds: 20
nodeSelector: {}
tolerations: []
affinity: {}

View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,6 @@
apiVersion: v2
name: web
description: A Helm chart for Kubernetes
type: application
version: 0.0.0
appVersion: "0.7.0-canary.18"

View File

@@ -0,0 +1,16 @@
1. Get the application URL by running these commands:
{{- if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "web.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "web.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "web.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "web.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@@ -0,0 +1,63 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "web.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "web.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "web.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "web.labels" -}}
helm.sh/chart: {{ include "web.chart" . }}
{{ include "web.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
monitoring: enabled
{{- end }}
{{/*
Selector labels
*/}}
{{- define "web.selectorLabels" -}}
app.kubernetes.io/name: {{ include "web.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "web.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "web.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,60 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "web.fullname" . }}
labels:
{{- include "web.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "web.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "web.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "web.serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: AFFINE_ENV
value: "{{ .Release.Namespace }}"
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "web.fullname" . }}
labels:
{{- include "web.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "web.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "web.serviceAccountName" . }}
labels:
{{- include "web.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "web.fullname" . }}-test-connection"
labels:
{{- include "web.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "web.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

View File

@@ -0,0 +1,37 @@
replicaCount: 1
image:
repository: ghcr.io/toeverything/affine-front
pullPolicy: IfNotPresent
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: "affine-web"
podAnnotations: {}
podSecurityContext:
fsGroup: 2000
resources:
limits:
cpu: '500m'
memory: 2Gi
requests:
cpu: '500m'
memory: 2Gi
nodeSelector: {}
tolerations: []
affinity: {}
probe:
initialDelaySeconds: 1

View File

@@ -36,44 +36,42 @@ spec:
{{- end }} {{- end }}
{{- end }} {{- end }}
rules: rules:
{{- range .Values.global.ingress.hosts }} - host: "{{ .Values.global.ingress.host }}"
- host: {{ . | quote }}
http: http:
paths: paths:
- path: /socket.io - path: /socket.io
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: {{ $.Values.front.services.sync.name }} name: affine-sync
port: port:
number: {{ $.Values.front.services.sync.port }} number: {{ .Values.sync.service.port }}
- path: /graphql - path: /graphql
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: affine-graphql name: affine-graphql
port: port:
number: {{ $.Values.graphql.service.port }} number: {{ .Values.graphql.service.port }}
- path: /api - path: /api
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: affine-graphql name: affine-graphql
port: port:
number: {{ $.Values.graphql.service.port }} number: {{ .Values.graphql.service.port }}
- path: /workspace - path: /workspace
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: {{ $.Values.front.services.renderer.name }} name: affine-renderer
port: port:
number: {{ $.Values.front.services.renderer.port }} number: {{ .Values.renderer.service.port }}
- path: / - path: /
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: {{ $.Values.front.services.web.name }} name: affine-web
port: port:
number: {{ $.Values.front.services.web.port }} number: {{ .Values.web.service.port }}
{{- end }}
{{- end }} {{- end }}

View File

@@ -4,13 +4,7 @@ global:
ingress: ingress:
enabled: false enabled: false
className: '' className: ''
# hosts for ingress rules host: affine.pro
# e.g.
# hosts:
# - affine.pro
# - www.affine.pro
hosts:
- affine.pro
tls: [] tls: []
secret: secret:
secretName: 'server-private-key' secretName: 'server-private-key'
@@ -47,25 +41,27 @@ graphql:
annotations: annotations:
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}' cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
front: sync:
services: service:
sync: type: ClusterIP
name: affine-sync port: 3010
type: ClusterIP annotations:
port: 3010 cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
annotations:
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}' renderer:
renderer: service:
name: affine-renderer type: ClusterIP
type: ClusterIP port: 3000
port: 3000 annotations:
annotations: cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
web: doc:
name: affine-web service:
type: ClusterIP type: ClusterIP
port: 8080 annotations:
doc: cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
type: ClusterIP
annotations: web:
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}' service:
type: ClusterIP
port: 8080

View File

@@ -1,10 +1,6 @@
name: 'Pull Request Labeler' name: 'Pull Request Labeler'
on: on:
pull_request_target: - pull_request_target
types:
- opened
- reopened
- synchronize
jobs: jobs:
triage: triage:

View File

@@ -3,13 +3,7 @@ name: Build Images
on: on:
workflow_call: workflow_call:
inputs: inputs:
build-type: flavor:
type: string
required: true
app-version:
type: string
required: true
git-short-hash:
type: string type: string
required: true required: true
@@ -22,13 +16,12 @@ jobs:
build-web: build-web:
name: Build @affine/web name: Build @affine/web
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: ${{ inputs.build-type }} environment: ${{ github.event.inputs.flavor }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- name: Build Core - name: Build Core
@@ -37,14 +30,15 @@ jobs:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
BUILD_TYPE: ${{ inputs.build-type }} BUILD_TYPE: ${{ github.event.inputs.flavor }}
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }} CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine-web' SENTRY_PROJECT: 'affine-web'
SENTRY_RELEASE: ${{ inputs.app-version }} SENTRY_RELEASE: ${{ steps.version.outputs.APP_VERSION }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
PERFSEE_TOKEN: ${{ secrets.PERFSEE_TOKEN }} PERFSEE_TOKEN: ${{ secrets.PERFSEE_TOKEN }}
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
- name: Upload web artifact - name: Upload web artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -55,13 +49,12 @@ jobs:
build-admin: build-admin:
name: Build @affine/admin name: Build @affine/admin
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: ${{ inputs.build-type }} environment: ${{ github.event.inputs.flavor }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- name: Build Admin - name: Build Admin
@@ -70,13 +63,14 @@ jobs:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
BUILD_TYPE: ${{ inputs.build-type }} BUILD_TYPE: ${{ github.event.inputs.flavor }}
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }} CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine-admin' SENTRY_PROJECT: 'affine-admin'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
PERFSEE_TOKEN: ${{ secrets.PERFSEE_TOKEN }} PERFSEE_TOKEN: ${{ secrets.PERFSEE_TOKEN }}
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
- name: Upload admin artifact - name: Upload admin artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -87,13 +81,12 @@ jobs:
build-mobile: build-mobile:
name: Build @affine/mobile name: Build @affine/mobile
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: ${{ inputs.build-type }} environment: ${{ github.event.inputs.flavor }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- name: Build Mobile - name: Build Mobile
@@ -102,13 +95,14 @@ jobs:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
BUILD_TYPE: ${{ inputs.build-type }} BUILD_TYPE: ${{ github.event.inputs.flavor }}
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }} CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine-mobile' SENTRY_PROJECT: 'affine-mobile'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
PERFSEE_TOKEN: ${{ secrets.PERFSEE_TOKEN }} PERFSEE_TOKEN: ${{ secrets.PERFSEE_TOKEN }}
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
- name: Upload mobile artifact - name: Upload mobile artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -119,7 +113,6 @@ jobs:
build-server-native: build-server-native:
name: Build Server native - ${{ matrix.targets.name }} name: Build Server native - ${{ matrix.targets.name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: ${{ inputs.build-type }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -134,9 +127,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
@@ -168,9 +160,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
@@ -184,6 +175,8 @@ jobs:
path: ./packages/backend/native path: ./packages/backend/native
- name: List server-native files - name: List server-native files
run: ls -alh ./packages/backend/native run: ls -alh ./packages/backend/native
- name: Build @affine/reader
run: yarn workspace @affine/reader build
- name: Build Server - name: Build Server
run: yarn workspace @affine/server build run: yarn workspace @affine/server build
- name: Upload server dist - name: Upload server dist
@@ -208,6 +201,16 @@ jobs:
with: with:
name: server-dist name: server-dist
path: ./packages/backend/server/dist path: ./packages/backend/server/dist
- name: Setup env
run: |
echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
if [ -z "${{ inputs.flavor }}" ]
then
echo "RELEASE_FLAVOR=canary" >> "$GITHUB_ENV"
else
echo "RELEASE_FLAVOR=${{ inputs.flavor }}" >> "$GITHUB_ENV"
fi
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@@ -259,11 +262,21 @@ jobs:
run: mv ./node_modules ./packages/backend/server run: mv ./node_modules ./packages/backend/server
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Build backend Dockerfile - name: Build front Dockerfile
uses: docker/build-push-action@v6
with:
context: .
push: true
pull: true
platforms: linux/amd64,linux/arm64
provenance: true
file: .github/deployment/front/Dockerfile
tags: ghcr.io/toeverything/affine-front:${{env.RELEASE_FLAVOR}}-${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-front:${{env.RELEASE_FLAVOR}}
- name: Build graphql Dockerfile
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
@@ -272,4 +285,4 @@ jobs:
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/arm/v7
provenance: true provenance: true
file: .github/deployment/node/Dockerfile file: .github/deployment/node/Dockerfile
tags: ghcr.io/toeverything/affine:${{inputs.build-type}}-${{ inputs.git-short-hash }} tags: ghcr.io/toeverything/affine-graphql:${{env.RELEASE_FLAVOR}}-${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-graphql:${{env.RELEASE_FLAVOR}}

View File

@@ -0,0 +1,25 @@
name: Build Selfhost Image
on:
workflow_dispatch:
inputs:
flavor:
description: 'Select distribution to build'
type: choice
default: canary
options:
- canary
- beta
- stable
permissions:
contents: 'write'
id-token: 'write'
packages: 'write'
jobs:
build-image:
name: Build Image
uses: ./.github/workflows/build-images.yml
with:
flavor: ${{ github.event.inputs.flavor }}

View File

@@ -11,7 +11,6 @@ on:
paths-ignore: paths-ignore:
- README.md - README.md
pull_request: pull_request:
merge_group:
env: env:
DEBUG: napi:* DEBUG: napi:*
@@ -19,20 +18,34 @@ env:
APP_NAME: affine APP_NAME: affine
AFFINE_ENV: dev AFFINE_ENV: dev
COVERAGE: true COVERAGE: true
MACOSX_DEPLOYMENT_TARGET: '11.6' MACOSX_DEPLOYMENT_TARGET: '10.13'
DEPLOYMENT_TYPE: affine DEPLOYMENT_TYPE: affine
AFFINE_INDEXER_ENABLED: true
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
optimize_ci:
name: Optimize CI
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check_skip.outputs.skip }}
steps:
- uses: actions/checkout@v4
- name: Graphite CI Optimizer
uses: withgraphite/graphite-ci-action@main
id: check_skip
with:
graphite_token: ${{ secrets.GRAPHITE_CI_OPTIMIZER_TOKEN }}
analyze: analyze:
name: Analyze name: Analyze
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NODE_OPTIONS: --max-old-space-size=14384 NODE_OPTIONS: --max-old-space-size=14384
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
permissions: permissions:
actions: read actions: read
contents: read contents: read
@@ -66,6 +79,9 @@ jobs:
lint: lint:
name: Lint name: Lint
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Run oxlint - name: Run oxlint
@@ -91,6 +107,8 @@ jobs:
typecheck: typecheck:
name: Typecheck name: Typecheck
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
NODE_OPTIONS: --max-old-space-size=14384 NODE_OPTIONS: --max-old-space-size=14384
steps: steps:
@@ -118,6 +136,8 @@ jobs:
lint-rust: lint-rust:
name: Lint Rust name: Lint Rust
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ./.github/actions/build-rust - uses: ./.github/actions/build-rust
@@ -131,14 +151,15 @@ jobs:
- name: Clippy - name: Clippy
run: | run: |
rustup component add clippy rustup component add clippy
cargo clippy --workspace --exclude affine_server_native --all-targets --all-features -- -D warnings cargo clippy --all-targets --all-features -- -D warnings
cargo clippy -p affine_server_native --all-targets --all-features -- -D warnings
check-git-status: check-git-status:
name: Check Git Status name: Check Git Status
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- optimize_ci
- build-server-native - build-server-native
if: needs.optimize_ci.outputs.skip == 'false'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
@@ -152,6 +173,11 @@ jobs:
name: server-native.node name: server-native.node
path: ./packages/backend/native path: ./packages/backend/native
- name: Bundle @affine/reader
shell: bash
run: |
yarn workspace @affine/reader build
- name: Run Check - name: Run Check
run: | run: |
yarn affine init yarn affine init
@@ -169,6 +195,8 @@ jobs:
check-yarn-binary: check-yarn-binary:
name: Check yarn binary name: Check yarn binary
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Run check - name: Run check
@@ -179,10 +207,12 @@ jobs:
e2e-blocksuite-test: e2e-blocksuite-test:
name: E2E BlockSuite Test name: E2E BlockSuite Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
shard: [1, 2] shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
@@ -210,13 +240,20 @@ jobs:
e2e-blocksuite-cross-browser-test: e2e-blocksuite-cross-browser-test:
name: E2E BlockSuite Cross Browser Test name: E2E BlockSuite Cross Browser Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
strategy:
fail-fast: false
matrix:
shard: [1, 2]
browser: ['chromium', 'firefox', 'webkit']
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
playwright-install: true playwright-install: true
playwright-platform: 'chromium,firefox,webkit' playwright-platform: ${{ matrix.browser }}
electron-install: false electron-install: false
full-cache: true full-cache: true
@@ -224,67 +261,23 @@ jobs:
run: yarn workspace @blocksuite/playground build run: yarn workspace @blocksuite/playground build
- name: Run playwright tests - name: Run playwright tests
run: | env:
yarn workspace @blocksuite/integration-test test:unit BROWSER: ${{ matrix.browser }}
yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only run: yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
- name: Upload test results - name: Upload test results
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: test-results-e2e-bs-cross-browser name: test-results-e2e-bs-cross-browser-${{ matrix.browser }}-${{ matrix.shard }}
path: ./test-results path: ./test-results
if-no-files-found: ignore if-no-files-found: ignore
bundler-matrix:
name: Bundler Matrix (${{ matrix.bundler }})
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
bundler: [webpack, rspack]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
playwright-install: false
electron-install: false
full-cache: true
- name: Run frontend build matrix
env:
AFFINE_BUNDLER: ${{ matrix.bundler }}
run: |
set -euo pipefail
packages=(
"@affine/web"
"@affine/mobile"
"@affine/ios"
"@affine/android"
"@affine/admin"
"@affine/electron-renderer"
)
summary="test-results-bundler-${AFFINE_BUNDLER}.txt"
: > "$summary"
for pkg in "${packages[@]}"; do
start=$(date +%s)
yarn affine "$pkg" build
end=$(date +%s)
echo "${pkg},$((end-start))" >> "$summary"
done
- name: Upload bundler timing
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-bundler-${{ matrix.bundler }}
path: ./test-results-bundler-${{ matrix.bundler }}.txt
if-no-files-found: ignore
e2e-test: e2e-test:
name: E2E Test name: E2E Test
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
DISTRIBUTION: web DISTRIBUTION: web
IN_CI_TEST: true IN_CI_TEST: true
@@ -292,7 +285,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
shard: [1, 2, 3, 4, 5] shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
@@ -317,13 +310,15 @@ jobs:
e2e-mobile-test: e2e-mobile-test:
name: E2E Mobile Test name: E2E Mobile Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
DISTRIBUTION: mobile DISTRIBUTION: mobile
IN_CI_TEST: true IN_CI_TEST: true
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
shard: [1, 2] shard: [1, 2, 3, 4, 5]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
@@ -348,13 +343,15 @@ jobs:
name: Unit Test name: Unit Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- build-native-linux - optimize_ci
- build-native
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
DISTRIBUTION: web DISTRIBUTION: web
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
shard: [1, 2, 3] shard: [1, 2, 3, 4, 5]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
@@ -362,7 +359,6 @@ jobs:
with: with:
electron-install: true electron-install: true
playwright-install: true playwright-install: true
playwright-platform: 'chromium,firefox,webkit'
full-cache: true full-cache: true
- name: Download affine.linux-x64-gnu.node - name: Download affine.linux-x64-gnu.node
@@ -383,47 +379,18 @@ jobs:
name: affine name: affine
fail_ci_if_error: false fail_ci_if_error: false
build-native-linux: build-native:
name: Build AFFiNE native (x86_64-unknown-linux-gnu)
runs-on: ubuntu-latest
env:
CARGO_PROFILE_RELEASE_DEBUG: '1'
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/native
electron-install: false
- name: Setup filename
id: filename
working-directory: ${{ github.workspace }}
shell: bash
run: |
export PLATFORM_ARCH_ABI=$(node -e "console.log(require('@napi-rs/cli').parseTriple('x86_64-unknown-linux-gnu').platformArchABI)")
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
target: x86_64-unknown-linux-gnu
package: '@affine/native'
- name: Upload ${{ steps.filename.outputs.filename }}
uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ steps.filename.outputs.filename }}
path: ${{ github.workspace }}/packages/frontend/native/${{ steps.filename.outputs.filename }}
if-no-files-found: error
build-native-macos:
name: Build AFFiNE native (${{ matrix.spec.target }}) name: Build AFFiNE native (${{ matrix.spec.target }})
runs-on: ${{ matrix.spec.os }} runs-on: ${{ matrix.spec.os }}
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
CARGO_PROFILE_RELEASE_DEBUG: '1' CARGO_PROFILE_RELEASE_DEBUG: '1'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
spec: spec:
- { os: ubuntu-latest, target: x86_64-unknown-linux-gnu }
- { os: macos-latest, target: x86_64-apple-darwin } - { os: macos-latest, target: x86_64-apple-darwin }
- { os: macos-latest, target: aarch64-apple-darwin } - { os: macos-latest, target: aarch64-apple-darwin }
@@ -456,9 +423,11 @@ jobs:
# Split Windows build because it's too slow # Split Windows build because it's too slow
# and other ci jobs required linux native # and other ci jobs required linux native
build-native-windows: build-windows-native:
name: Build AFFiNE native (${{ matrix.spec.target }}) name: Build AFFiNE native (${{ matrix.spec.target }})
runs-on: ${{ matrix.spec.os }} runs-on: ${{ matrix.spec.os }}
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
CARGO_PROFILE_RELEASE_DEBUG: '1' CARGO_PROFILE_RELEASE_DEBUG: '1'
strategy: strategy:
@@ -506,6 +475,8 @@ jobs:
build-server-native: build-server-native:
name: Build Server native name: Build Server native
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
CARGO_PROFILE_RELEASE_DEBUG: '1' CARGO_PROFILE_RELEASE_DEBUG: '1'
steps: steps:
@@ -531,6 +502,8 @@ jobs:
build-electron-renderer: build-electron-renderer:
name: Build @affine/electron renderer name: Build @affine/electron renderer
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
@@ -556,7 +529,9 @@ jobs:
name: Native Unit Test name: Native Unit Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- build-native-linux - optimize_ci
- build-native
if: needs.optimize_ci.outputs.skip == 'false'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
@@ -576,12 +551,14 @@ jobs:
name: Server Test name: Server Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- optimize_ci
- build-server-native - build-server-native
if: needs.optimize_ci.outputs.skip == 'false'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
node_index: [0, 1, 2, 3] node_index: [0, 1, 2, 3, 4, 5, 6, 7]
total_nodes: [4] total_nodes: [8]
env: env:
NODE_ENV: test NODE_ENV: test
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
@@ -608,7 +585,7 @@ jobs:
- 1025:1025 - 1025:1025
- 8025:8025 - 8025:8025
indexer: indexer:
image: manticoresearch/manticore:10.1.0 image: manticoresearch/manticore:9.3.2
ports: ports:
- 9308:9308 - 9308:9308
steps: steps:
@@ -649,7 +626,11 @@ jobs:
name: Server Test with Elasticsearch name: Server Test with Elasticsearch
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- optimize_ci
- build-server-native - build-server-native
if: needs.optimize_ci.outputs.skip == 'false'
strategy:
fail-fast: false
env: env:
NODE_ENV: test NODE_ENV: test
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
@@ -730,7 +711,9 @@ jobs:
name: Server E2E Test name: Server E2E Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- optimize_ci
- build-server-native - build-server-native
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
NODE_ENV: test NODE_ENV: test
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
@@ -752,7 +735,7 @@ jobs:
ports: ports:
- 6379:6379 - 6379:6379
indexer: indexer:
image: manticoresearch/manticore:10.1.0 image: manticoresearch/manticore:9.3.2
ports: ports:
- 9308:9308 - 9308:9308
steps: steps:
@@ -788,6 +771,9 @@ jobs:
miri: miri:
name: miri code check name: miri code check
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
RUST_BACKTRACE: full RUST_BACKTRACE: full
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
@@ -801,9 +787,7 @@ jobs:
toolchain: nightly toolchain: nightly
components: miri components: miri
- name: Install latest nextest release - name: Install latest nextest release
uses: taiki-e/install-action@v2 uses: taiki-e/install-action@nextest
with:
tool: nextest@0.9.98
- name: Miri Code Check - name: Miri Code Check
continue-on-error: true continue-on-error: true
@@ -813,6 +797,9 @@ jobs:
loom: loom:
name: loom thread test name: loom thread test
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
RUSTFLAGS: --cfg loom RUSTFLAGS: --cfg loom
RUST_BACKTRACE: full RUST_BACKTRACE: full
@@ -825,9 +812,7 @@ jobs:
with: with:
toolchain: stable toolchain: stable
- name: Install latest nextest release - name: Install latest nextest release
uses: taiki-e/install-action@v2 uses: taiki-e/install-action@nextest
with:
tool: nextest@0.9.98
- name: Loom Thread Test - name: Loom Thread Test
run: | run: |
@@ -836,7 +821,11 @@ jobs:
fuzzing: fuzzing:
name: fuzzing name: fuzzing
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
RUSTFLAGS: -D warnings
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -869,9 +858,57 @@ jobs:
name: fuzz-artifact name: fuzz-artifact
path: packages/common/y-octo/utils/fuzz/artifacts/**/* path: packages/common/y-octo/utils/fuzz/artifacts/**/*
y-octo-binding-test:
name: y-octo binding test on ${{ matrix.settings.target }}
runs-on: ${{ matrix.settings.os }}
strategy:
fail-fast: false
matrix:
settings:
- { target: 'x86_64-unknown-linux-gnu', os: 'ubuntu-latest' }
- { target: 'aarch64-unknown-linux-gnu', os: 'ubuntu-24.04-arm' }
- { target: 'x86_64-apple-darwin', os: 'macos-13' }
- { target: 'aarch64-apple-darwin', os: 'macos-latest' }
- { target: 'x86_64-pc-windows-msvc', os: 'windows-latest' }
- { target: 'aarch64-pc-windows-msvc', os: 'windows-11-arm' }
needs:
- optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine-tools/cli @affine/monorepo @y-octo/node
electron-install: false
- name: Install rustup (Windows 11 ARM)
if: matrix.settings.os == 'windows-11-arm'
shell: pwsh
run: |
Invoke-WebRequest -Uri "https://static.rust-lang.org/rustup/dist/aarch64-pc-windows-msvc/rustup-init.exe" -OutFile rustup-init.exe
.\rustup-init.exe --default-toolchain none -y
"$env:USERPROFILE\.cargo\bin" | Out-File -Append -Encoding ascii $env:GITHUB_PATH
"CARGO_HOME=$env:USERPROFILE\.cargo" | Out-File -Append -Encoding ascii $env:GITHUB_ENV
- name: Install Rust (Windows 11 ARM)
if: matrix.settings.os == 'windows-11-arm'
shell: pwsh
run: |
rustup install stable
rustup target add ${{ matrix.settings.target }}
cargo --version
- name: Build Rust
uses: ./.github/actions/build-rust
with:
target: ${{ matrix.settings.target }}
package: '@y-octo/node'
- name: Run tests
run: yarn affine @y-octo/node test
rust-test: rust-test:
name: Run native tests name: Run native tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
steps: steps:
@@ -883,58 +920,18 @@ jobs:
no-build: 'true' no-build: 'true'
- name: Install latest nextest release - name: Install latest nextest release
uses: taiki-e/install-action@v2 uses: taiki-e/install-action@nextest
with:
tool: nextest@0.9.98
- name: Run tests - name: Run tests
run: cargo nextest run --workspace --exclude affine_server_native --features use-as-lib --release --no-fail-fast run: cargo nextest run --release --no-fail-fast
copilot-test-filter:
name: Copilot test filter
runs-on: ubuntu-latest
outputs:
run-api: ${{ steps.decision.outputs.run_api }}
run-e2e: ${{ steps.decision.outputs.run_e2e }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: copilot-filter
with:
filters: |
api:
- 'packages/backend/server/src/plugins/copilot/**'
- 'packages/backend/server/tests/copilot.*'
e2e:
- 'packages/backend/server/src/plugins/copilot/**'
- 'packages/backend/server/tests/copilot.*'
- 'packages/frontend/core/src/blocksuite/ai/**'
- 'packages/frontend/core/src/modules/workspace-indexer-embedding/**'
- 'tests/affine-cloud-copilot/**'
- name: Decide test scope
id: decision
run: |
if [[ "${{ steps.copilot-filter.outputs.api }}" == "true" ]]; then
echo "run_api=true" >> "$GITHUB_OUTPUT"
else
echo "run_api=false" >> "$GITHUB_OUTPUT"
fi
if [[ "${{ steps.copilot-filter.outputs.e2e }}" == "true" ]]; then
echo "run_e2e=true" >> "$GITHUB_OUTPUT"
else
echo "run_e2e=false" >> "$GITHUB_OUTPUT"
fi
copilot-api-test: copilot-api-test:
name: Server Copilot Api Test name: Server Copilot Api Test
if: ${{ needs.copilot-test-filter.outputs.run-api == 'true' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- optimize_ci
- build-server-native - build-server-native
- copilot-test-filter if: needs.optimize_ci.outputs.skip == 'false'
env: env:
NODE_ENV: test NODE_ENV: test
DISTRIBUTION: web DISTRIBUTION: web
@@ -962,35 +959,59 @@ jobs:
- 1025:1025 - 1025:1025
- 8025:8025 - 8025:8025
indexer: indexer:
image: manticoresearch/manticore:10.1.0 image: manticoresearch/manticore:9.3.2
ports: ports:
- 9308:9308 - 9308:9308
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Check blocksuite update
id: check-blocksuite-update
env:
BASE_REF: ${{ github.base_ref }}
run: |
if node ./scripts/detect-blocksuite-update.mjs "$BASE_REF"; then
echo "skip=false" >> $GITHUB_OUTPUT
else
echo "skip=true" >> $GITHUB_OUTPUT
fi
- uses: dorny/paths-filter@v3
id: apifilter
with:
filters: |
changed:
- 'packages/backend/server/src/plugins/copilot/**'
- 'packages/backend/server/tests/copilot.*'
- name: Setup Node.js - name: Setup Node.js
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
electron-install: false electron-install: false
full-cache: true full-cache: true
- name: Download server-native.node - name: Download server-native.node
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: server-native.node name: server-native.node
path: ./packages/backend/native path: ./packages/backend/native
- name: Prepare Server Test Environment - name: Prepare Server Test Environment
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
env: env:
SERVER_CONFIG: ${{ secrets.TEST_SERVER_CONFIG }} SERVER_CONFIG: ${{ secrets.TEST_SERVER_CONFIG }}
uses: ./.github/actions/server-test-env uses: ./.github/actions/server-test-env
- name: Run server tests - name: Run server tests
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
run: yarn affine @affine/server test:copilot:coverage --forbid-only run: yarn affine @affine/server test:copilot:coverage --forbid-only
env: env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target' CARGO_TARGET_DIR: '${{ github.workspace }}/target'
- name: Upload server test coverage results - name: Upload server test coverage results
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
@@ -1001,7 +1022,6 @@ jobs:
copilot-e2e-test: copilot-e2e-test:
name: Frontend Copilot E2E Test name: Frontend Copilot E2E Test
if: ${{ needs.copilot-test-filter.outputs.run-e2e == 'true' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
DISTRIBUTION: web DISTRIBUTION: web
@@ -1012,11 +1032,10 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
shardIndex: [1, 2, 3, 4, 5] shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [5] shardTotal: [8]
needs: needs:
- build-server-native - build-server-native
- copilot-test-filter
services: services:
postgres: postgres:
image: pgvector/pgvector:pg16 image: pgvector/pgvector:pg16
@@ -1034,13 +1053,36 @@ jobs:
ports: ports:
- 6379:6379 - 6379:6379
indexer: indexer:
image: manticoresearch/manticore:10.1.0 image: manticoresearch/manticore:9.3.2
ports: ports:
- 9308:9308 - 9308:9308
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Check blocksuite update
id: check-blocksuite-update
env:
BASE_REF: ${{ github.base_ref }}
run: |
if node ./scripts/detect-blocksuite-update.mjs "$BASE_REF"; then
echo "skip=false" >> $GITHUB_OUTPUT
else
echo "skip=true" >> $GITHUB_OUTPUT
fi
- uses: dorny/paths-filter@v3
id: e2efilter
with:
filters: |
changed:
- 'packages/backend/server/src/plugins/copilot/**'
- 'packages/backend/server/tests/copilot.*'
- 'packages/frontend/core/src/blocksuite/ai/**'
- 'packages/frontend/core/src/modules/workspace-indexer-embedding/**'
- 'tests/affine-cloud-copilot/**'
- name: Setup Node.js - name: Setup Node.js
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
playwright-install: true playwright-install: true
@@ -1049,17 +1091,20 @@ jobs:
hard-link-nm: false hard-link-nm: false
- name: Download server-native.node - name: Download server-native.node
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: server-native.node name: server-native.node
path: ./packages/backend/native path: ./packages/backend/native
- name: Prepare Server Test Environment - name: Prepare Server Test Environment
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
env: env:
SERVER_CONFIG: ${{ secrets.TEST_SERVER_CONFIG }} SERVER_CONFIG: ${{ secrets.TEST_SERVER_CONFIG }}
uses: ./.github/actions/server-test-env uses: ./.github/actions/server-test-env
- name: Run Copilot E2E Test ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - name: Run Copilot E2E Test ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
uses: ./.github/actions/copilot-test uses: ./.github/actions/copilot-test
with: with:
script: yarn affine @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} script: yarn affine @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
@@ -1068,8 +1113,10 @@ jobs:
name: ${{ matrix.tests.name }} name: ${{ matrix.tests.name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- optimize_ci
- build-server-native - build-server-native
- build-native-linux - build-native
if: needs.optimize_ci.outputs.skip == 'false'
env: env:
DISTRIBUTION: web DISTRIBUTION: web
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
@@ -1079,12 +1126,24 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
tests: tests:
- name: 'Cloud E2E Test 1/2' - name: 'Cloud E2E Test 1/6'
shard: 1 shard: 1
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=1/2 script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=1/6
- name: 'Cloud E2E Test 2/2' - name: 'Cloud E2E Test 2/6'
shard: 2 shard: 2
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=2/2 script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=2/6
- name: 'Cloud E2E Test 3/6'
shard: 3
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=3/6
- name: 'Cloud E2E Test 4/6'
shard: 4
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=4/6
- name: 'Cloud E2E Test 5/6'
shard: 5
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=5/6
- name: 'Cloud E2E Test 6/6'
shard: 6
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=6/6
- name: 'Cloud Desktop E2E Test' - name: 'Cloud Desktop E2E Test'
shard: desktop shard: desktop
script: | script: |
@@ -1116,7 +1175,7 @@ jobs:
- 1025:1025 - 1025:1025
- 8025:8025 - 8025:8025
indexer: indexer:
image: manticoresearch/manticore:10.1.0 image: manticoresearch/manticore:9.3.2
ports: ports:
- 9308:9308 - 9308:9308
steps: steps:
@@ -1161,10 +1220,10 @@ jobs:
name: Desktop Test (${{ matrix.spec.os }}, ${{ matrix.spec.platform }}, ${{ matrix.spec.arch }}, ${{ matrix.spec.target }}, ${{ matrix.spec.test }}) name: Desktop Test (${{ matrix.spec.os }}, ${{ matrix.spec.platform }}, ${{ matrix.spec.arch }}, ${{ matrix.spec.target }}, ${{ matrix.spec.test }})
runs-on: ${{ matrix.spec.os }} runs-on: ${{ matrix.spec.os }}
needs: needs:
- optimize_ci
- build-electron-renderer - build-electron-renderer
- build-native-linux - build-native
- build-native-macos if: needs.optimize_ci.outputs.skip == 'false'
- build-native-windows
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -1247,6 +1306,86 @@ jobs:
if: ${{ matrix.spec.test && matrix.spec.os != 'ubuntu-latest' }} if: ${{ matrix.spec.test && matrix.spec.os != 'ubuntu-latest' }}
run: yarn affine @affine-test/affine-desktop e2e run: yarn affine @affine-test/affine-desktop e2e
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-e2e-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
path: ./test-results
if-no-files-found: ignore
desktop-bundle-check:
name: Desktop bundle check (${{ matrix.spec.os }}, ${{ matrix.spec.platform }}, ${{ matrix.spec.arch }}, ${{ matrix.spec.target }}, ${{ matrix.spec.test }})
runs-on: ${{ matrix.spec.os }}
needs:
- optimize_ci
- build-electron-renderer
- build-native
if: needs.optimize_ci.outputs.skip == 'false'
strategy:
fail-fast: false
matrix:
spec:
- {
os: macos-latest,
platform: macos,
arch: x64,
target: x86_64-apple-darwin,
test: false,
}
- {
os: macos-latest,
platform: macos,
arch: arm64,
target: aarch64-apple-darwin,
test: true,
}
- {
os: ubuntu-latest,
platform: linux,
arch: x64,
target: x86_64-unknown-linux-gnu,
test: true,
}
- {
os: windows-latest,
platform: windows,
arch: x64,
target: x86_64-pc-windows-msvc,
test: true,
}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: ./.github/actions/setup-node
timeout-minutes: 10
with:
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine-test/affine-desktop @affine/nbstore @toeverything/infra
playwright-install: true
hard-link-nm: false
enableScripts: false
- name: Setup filename
id: filename
shell: bash
run: |
export PLATFORM_ARCH_ABI=$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Download ${{ steps.filename.outputs.filename }}
uses: actions/download-artifact@v4
with:
name: ${{ steps.filename.outputs.filename }}
path: ./packages/frontend/native
- name: Download web artifact
uses: ./.github/actions/download-web
with:
path: packages/frontend/apps/electron/resources/web-static
- name: Build Desktop Layers
run: yarn affine @affine/electron build
- name: Make bundle (macOS) - name: Make bundle (macOS)
if: ${{ matrix.spec.target == 'aarch64-apple-darwin' }} if: ${{ matrix.spec.target == 'aarch64-apple-darwin' }}
env: env:
@@ -1271,7 +1410,7 @@ jobs:
run: | run: |
sudo add-apt-repository universe sudo add-apt-repository universe
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak update flatpak update
# some flatpak deps need git protocol.file.allow # some flatpak deps need git protocol.file.allow
git config --global protocol.file.allow always git config --global protocol.file.allow always
@@ -1286,13 +1425,16 @@ jobs:
run: | run: |
yarn affine @affine/electron node ./scripts/macos-arm64-output-check.ts yarn affine @affine/electron node ./scripts/macos-arm64-output-check.ts
- name: Upload test results test-build-mobile-app:
if: always() uses: ./.github/workflows/release-mobile.yml
uses: actions/upload-artifact@v4 needs: optimize_ci
with: if: needs.optimize_ci.outputs.skip == 'false'
name: test-results-e2e-${{ matrix.spec.os }}-${{ matrix.spec.arch }} with:
path: ./test-results build-type: canary
if-no-files-found: ignore build-target: development
secrets: inherit
permissions:
id-token: 'write'
test-done: test-done:
needs: needs:
@@ -1307,23 +1449,24 @@ jobs:
- e2e-blocksuite-cross-browser-test - e2e-blocksuite-cross-browser-test
- e2e-mobile-test - e2e-mobile-test
- unit-test - unit-test
- build-native-linux - build-native
- build-native-macos - build-windows-native
- build-native-windows
- build-server-native - build-server-native
- build-electron-renderer - build-electron-renderer
- native-unit-test - native-unit-test
- miri - miri
- loom - loom
- fuzzing - fuzzing
- y-octo-binding-test
- server-test - server-test
- server-e2e-test - server-e2e-test
- rust-test - rust-test
- copilot-test-filter
- copilot-api-test - copilot-api-test
- copilot-e2e-test - copilot-e2e-test
- desktop-test - desktop-test
- desktop-bundle-check
- cloud-e2e-test - cloud-e2e-test
- test-build-mobile-app
if: always() if: always()
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: 3, 2, 1 Launch name: 3, 2, 1 Launch

View File

@@ -60,7 +60,7 @@ jobs:
- 1025:1025 - 1025:1025
- 8025:8025 - 8025:8025
indexer: indexer:
image: manticoresearch/manticore:10.1.0 image: manticoresearch/manticore:9.3.2
ports: ports:
- 9308:9308 - 9308:9308
steps: steps:
@@ -109,8 +109,8 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [10] shardTotal: [8]
needs: needs:
- build-server-native - build-server-native
services: services:
@@ -130,7 +130,7 @@ jobs:
ports: ports:
- 6379:6379 - 6379:6379
indexer: indexer:
image: manticoresearch/manticore:10.1.0 image: manticoresearch/manticore:9.3.2
ports: ports:
- 9308:9308 - 9308:9308
steps: steps:

View File

@@ -0,0 +1,32 @@
name: Deploy Automatically
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+-canary.[0-9]+'
schedule:
- cron: '0 9 * * *'
permissions:
contents: write
pull-requests: write
actions: write
jobs:
dispatch-deploy:
runs-on: ubuntu-latest
name: Setup Deploy
steps:
- name: dispatch deploy by tag
if: ${{ github.event_name == 'push' }}
uses: benc-uk/workflow-dispatch@v1
with:
workflow: deploy.yml
inputs: '{ "flavor": "canary" }'
- name: dispatch deploy by schedule
if: ${{ github.event_name == 'schedule' }}
uses: benc-uk/workflow-dispatch@v1
with:
workflow: deploy.yml
inputs: '{ "flavor": "canary" }'
ref: canary

189
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,189 @@
name: Deploy
on:
workflow_dispatch:
inputs:
flavor:
description: 'Select what enverionment to deploy to'
type: choice
default: canary
options:
- canary
- beta
- stable
- internal
permissions:
contents: 'write'
id-token: 'write'
packages: 'write'
jobs:
output-prev-version:
name: Output previous version
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.flavor }}
outputs:
prev: ${{ steps.print.outputs.version }}
namespace: ${{ steps.print.outputs.namespace }}
steps:
- uses: actions/checkout@v4
- name: Auth to Cluster
uses: './.github/actions/cluster-auth'
with:
gcp-project-number: ${{ secrets.GCP_PROJECT_NUMBER }}
gcp-project-id: ${{ secrets.GCP_PROJECT_ID }}
service-account: ${{ secrets.GCP_HELM_DEPLOY_SERVICE_ACCOUNT }}
cluster-name: ${{ secrets.GCP_CLUSTER_NAME }}
cluster-location: ${{ secrets.GCP_CLUSTER_LOCATION }}
- name: Output previous version
id: print
run: |
namespace=""
if [ "${{ github.event.inputs.flavor }}" = "canary" ]; then
namespace="dev"
elif [ "${{ github.event.inputs.flavor }}" = "beta" ]; then
namespace="beta"
elif [ "${{ github.event.inputs.flavor }}" = "stable" ]; then
namespace="production"
else
echo "Invalid flavor: ${{ github.event.inputs.flavor }}"
exit 1
fi
echo "Namespace set to: $namespace"
# Get the previous version from the deployment
prev_version=$(kubectl get deployment -n $namespace affine-graphql -o=jsonpath='{.spec.template.spec.containers[0].image}' | awk -F '-' '{print $3}')
echo "Previous version: $prev_version"
echo "version=$prev_version" >> $GITHUB_OUTPUT
echo "namesapce=$namespace" >> $GITHUB_OUTPUT
build-images:
name: Build Images
uses: ./.github/workflows/build-images.yml
secrets: inherit
with:
flavor: ${{ github.event.inputs.flavor }}
deploy:
name: Deploy to cluster
if: ${{ github.event_name == 'workflow_dispatch' }}
environment: ${{ github.event.inputs.flavor }}
needs:
- build-images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Version
id: version
uses: ./.github/actions/setup-version
- name: Deploy to ${{ github.event.inputs.flavor }}
uses: ./.github/actions/deploy
with:
build-type: ${{ github.event.inputs.flavor }}
gcp-project-number: ${{ secrets.GCP_PROJECT_NUMBER }}
gcp-project-id: ${{ secrets.GCP_PROJECT_ID }}
service-account: ${{ secrets.GCP_HELM_DEPLOY_SERVICE_ACCOUNT }}
cluster-name: ${{ secrets.GCP_CLUSTER_NAME }}
cluster-location: ${{ secrets.GCP_CLUSTER_LOCATION }}
env:
APP_VERSION: ${{ steps.version.outputs.APP_VERSION }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
CANARY_DEPLOY_HOST: ${{ secrets.CANARY_DEPLOY_HOST }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }}
DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
DATABASE_NAME: ${{ secrets.DATABASE_NAME }}
GCLOUD_CONNECTION_NAME: ${{ secrets.GCLOUD_CONNECTION_NAME }}
REDIS_SERVER_HOST: ${{ secrets.REDIS_SERVER_HOST }}
REDIS_SERVER_PASSWORD: ${{ secrets.REDIS_SERVER_PASSWORD }}
CLOUD_SQL_IAM_ACCOUNT: ${{ secrets.CLOUD_SQL_IAM_ACCOUNT }}
APP_IAM_ACCOUNT: ${{ secrets.APP_IAM_ACCOUNT }}
STATIC_IP_NAME: ${{ secrets.STATIC_IP_NAME }}
AFFINE_INDEXER_SEARCH_PROVIDER: ${{ secrets.AFFINE_INDEXER_SEARCH_PROVIDER }}
AFFINE_INDEXER_SEARCH_ENDPOINT: ${{ secrets.AFFINE_INDEXER_SEARCH_ENDPOINT }}
AFFINE_INDEXER_SEARCH_API_KEY: ${{ secrets.AFFINE_INDEXER_SEARCH_API_KEY }}
deploy-done:
needs:
- output-prev-version
- build-images
- deploy
if: always()
runs-on: ubuntu-latest
name: Post deploy message
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/checkout@v4
with:
repository: toeverything/blocksuite
path: blocksuite
fetch-depth: 0
fetch-tags: true
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: 'workspaces focus @affine/changelog'
electron-install: false
- name: Output deployed info
if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
id: set_info
run: |
if [ "${{ github.event.inputs.flavor }}" = "canary" ]; then
echo "deployed_url=https://affine.fail" >> $GITHUB_OUTPUT
elif [ "${{ github.event.inputs.flavor }}" = "beta" ]; then
echo "deployed_url=https://insider.affine.pro" >> $GITHUB_OUTPUT
elif [ "${{ github.event.inputs.flavor }}" = "stable" ]; then
echo "deployed_url=https://app.affine.pro" >> $GITHUB_OUTPUT
else
exit 1
fi
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Post Success event to a Slack channel
if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
run: node ./tools/changelog/index.js
env:
CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
DEPLOYED_URL: ${{ steps.set_info.outputs.deployed_url }}
PREV_VERSION: ${{ needs.output-prev-version.outputs.prev }}
NAMESPACE: ${{ needs.output-prev-version.outputs.namespace }}
DEPLOYMENT: 'SERVER'
FLAVOR: ${{ github.event.inputs.flavor }}
BLOCKSUITE_REPO_PATH: ${{ github.workspace }}/blocksuite
- name: Post Failed event to a Slack channel
id: failed-slack
uses: slackapi/slack-github-action@v2.1.0
if: ${{ always() && contains(needs.*.result, 'failure') }}
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Backend deploy failed `${{ github.event.inputs.flavor }}`>"
blocks:
- type: section
text:
type: mrkdwn
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Backend deploy failed `${{ github.event.inputs.flavor }}`>"
- name: Post Cancel event to a Slack channel
id: cancel-slack
uses: slackapi/slack-github-action@v2.1.0
if: ${{ always() && contains(needs.*.result, 'cancelled') && !contains(needs.*.result, 'failure') }}
with:
token: ${{ secrets.SLACK_BOT_TOKEN }}
method: chat.postMessage
payload: |
channel: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Backend deploy cancelled `${{ github.event.inputs.flavor }}`>"
blocks:
- type: section
text:
type: mrkdwn
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Backend deploy cancelled `${{ github.event.inputs.flavor }}`>"

66
.github/workflows/helm-releaser.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Release Charts
on:
push:
branches: [canary]
paths:
- '.github/helm/**/Chart.yml'
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Checkout Helm chart repo
uses: actions/checkout@v4
with:
repository: toeverything/helm-charts
path: .helm-chart-repo
ref: gh-pages
token: ${{ secrets.HELM_RELEASER_TOKEN }}
- name: Install Helm
uses: azure/setup-helm@v4
- name: Install chart releaser
run: |
set -e
arch="$(dpkg --print-architecture)"
curl -s https://api.github.com/repos/helm/chart-releaser/releases/latest \
| yq --indent 0 --no-colors --input-format json --unwrapScalar \
".assets[] | select(.name | test("\""^chart-releaser_.+_linux_${arch}\.tar\.gz$"\"")) | .browser_download_url" \
| xargs curl -SsL \
| tar zxf - -C /usr/local/bin
- name: Package charts
working-directory: .helm-chart-repo
run: |
mkdir -p .cr-index
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm dependencies build ../.github/helm/affine
helm dependencies build ../.github/helm/affine-cloud
cr package ../.github/helm/affine
cr package ../.github/helm/affine-cloud
- name: Publish charts
working-directory: .helm-chart-repo
run: |
set -ex
git config --local user.name "$GITHUB_ACTOR"
git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com"
owner=$(cut -d '/' -f 1 <<< '${{ github.repository }}')
repo=helm-charts
git_hash=$(git rev-parse HEAD)
cr upload --commit "$git_hash" \
--git-repo "$repo" --owner "$owner" \
--token '${{ secrets.HELM_RELEASER_TOKEN }}' \
--skip-existing
cr index --git-repo "$repo" --owner "$owner" \
--token '${{ secrets.HELM_RELEASER_TOKEN }}' \
--index-path .cr-index --push

19
.github/workflows/label-checker.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Label Checker
on:
pull_request:
types:
- opened
- labeled
- unlabeled
branches:
- canary
jobs:
check_labels:
name: PR should not have a blocked label
runs-on: ubuntu-latest
steps:
- uses: docker://agilepathway/pull-request-label-checker:latest
with:
none_of: blocked
repo_token: ${{ secrets.GITHUB_TOKEN }}

12
.github/workflows/pr-auto-assign.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: Pull request auto assign
# on: pull_request
on:
pull_request:
types: [opened, ready_for_review]
jobs:
add-reviews:
runs-on: ubuntu-latest
steps:
- uses: kentaro-m/auto-assign-action@v2.0.0

View File

@@ -16,7 +16,6 @@ jobs:
check-pull-request-title: check-pull-request-title:
name: Check pull request title name: Check pull request title
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.action != 'edited' || github.event.changes.title != null }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js

View File

@@ -0,0 +1,38 @@
name: Release Desktop/Mobile Automatically
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+-canary.[0-9]+'
schedule:
- cron: '0 9 * * *'
permissions:
contents: write
pull-requests: write
actions: write
jobs:
dispatch-release-desktop:
runs-on: ubuntu-latest
name: Setup Release Desktop
steps:
- name: dispatch desktop release by tag
if: ${{ github.event_name == 'push' }}
uses: benc-uk/workflow-dispatch@v1
with:
workflow: release-desktop.yml
inputs: '{ "build-type": "canary", "is-draft": false, "is-pre-release": true }'
- name: dispatch desktop release by schedule
if: ${{ github.event_name == 'schedule' }}
uses: benc-uk/workflow-dispatch@v1
with:
workflow: release-desktop.yml
inputs: '{ "build-type": "canary", "is-draft": false, "is-pre-release": true }'
ref: canary
- name: dispatch desktop release by tag
uses: benc-uk/workflow-dispatch@v1
with:
workflow: release-mobile.yml
inputs: '{ "build-type": "canary", "build-target": "distribution" }'

View File

@@ -1,66 +0,0 @@
name: Release Cloud
on:
workflow_call:
inputs:
build-type:
required: true
type: string
app-version:
required: true
type: string
git-short-hash:
required: true
type: string
permissions:
contents: 'write'
id-token: 'write'
packages: 'write'
jobs:
build-images:
name: Build Images
uses: ./.github/workflows/build-images.yml
secrets: inherit
with:
build-type: ${{ inputs.build-type }}
app-version: ${{ inputs.app-version }}
git-short-hash: ${{ inputs.git-short-hash }}
deploy:
name: Deploy to cluster
environment: ${{ inputs.build-type }}
needs:
- build-images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to ${{ inputs.build-type }}
uses: ./.github/actions/deploy
with:
gcp-project-number: ${{ secrets.GCP_PROJECT_NUMBER }}
gcp-project-id: ${{ secrets.GCP_PROJECT_ID }}
service-account: ${{ secrets.GCP_HELM_DEPLOY_SERVICE_ACCOUNT }}
cluster-name: ${{ secrets.GCP_CLUSTER_NAME }}
cluster-location: ${{ secrets.GCP_CLUSTER_LOCATION }}
env:
BUILD_TYPE: ${{ inputs.build-type }}
APP_VERSION: ${{ inputs.app-version }}
GIT_SHORT_HASH: ${{ inputs.git-short-hash }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
CANARY_DEPLOY_HOST: ${{ secrets.CANARY_DEPLOY_HOST }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }}
DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
DATABASE_NAME: ${{ secrets.DATABASE_NAME }}
GCLOUD_CONNECTION_NAME: ${{ secrets.GCLOUD_CONNECTION_NAME }}
REDIS_SERVER_HOST: ${{ secrets.REDIS_SERVER_HOST }}
REDIS_SERVER_PASSWORD: ${{ secrets.REDIS_SERVER_PASSWORD }}
CLOUD_SQL_IAM_ACCOUNT: ${{ secrets.CLOUD_SQL_IAM_ACCOUNT }}
APP_IAM_ACCOUNT: ${{ secrets.APP_IAM_ACCOUNT }}
STATIC_IP_NAME: ${{ secrets.STATIC_IP_NAME }}
AFFINE_INDEXER_SEARCH_PROVIDER: ${{ secrets.AFFINE_INDEXER_SEARCH_PROVIDER }}
AFFINE_INDEXER_SEARCH_ENDPOINT: ${{ secrets.AFFINE_INDEXER_SEARCH_ENDPOINT }}
AFFINE_INDEXER_SEARCH_API_KEY: ${{ secrets.AFFINE_INDEXER_SEARCH_API_KEY }}

View File

@@ -1,225 +0,0 @@
name: Release Desktop Platform
on:
workflow_call:
inputs:
build_type:
required: true
type: string
app_version:
required: true
type: string
git_short_hash:
required: true
type: string
runner:
required: true
type: string
platform:
required: true
type: string
arch:
required: true
type: string
target:
required: true
type: string
apple_codesign:
required: false
default: false
type: boolean
install_linux_deps:
required: false
default: false
type: boolean
enable_scripts:
required: false
default: false
type: boolean
outputs:
files_to_be_signed:
description: Files to be signed (Windows only)
value: ${{ jobs.build.outputs.files_to_be_signed }}
permissions:
actions: write
contents: write
security-events: write
id-token: write
attestations: write
jobs:
build:
runs-on: ${{ inputs.runner }}
outputs:
files_to_be_signed: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED }}
env:
BUILD_TYPE: ${{ inputs.build_type }}
RELEASE_VERSION: ${{ inputs.app_version }}
DEBUG: 'affine:*,napi:*'
APP_NAME: affine
MACOSX_DEPLOYMENT_TARGET: '12.0'
SKIP_GENERATE_ASSETS: 1
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_RELEASE: ${{ inputs.app_version }}
steps:
- uses: actions/checkout@v4
- name: Setup Version
uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app_version }}
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
hard-link-nm: false
nmHoistingLimits: workspaces
enableScripts: ${{ inputs.enable_scripts }}
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
target: ${{ inputs.target }}
package: '@affine/native'
- uses: actions/download-artifact@v4
with:
name: desktop-web
path: packages/frontend/apps/electron/resources/web-static
- name: Build Desktop Layers
run: yarn affine @affine/electron build
- name: Signing By Apple Developer ID
if: ${{ inputs.platform == 'darwin' && inputs.apple_codesign }}
uses: apple-actions/import-codesign-certs@v5
with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
- name: Install additional dependencies on Linux
if: ${{ inputs.platform == 'linux' && inputs.install_linux_deps }}
run: |
df -h
sudo add-apt-repository universe
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak update
# some flatpak deps need git protocol.file.allow
git config --global protocol.file.allow always
# clean up apt cache to save disk space
sudo -E apt-get -y purge azure-cli* zulu* hhvm* llvm* firefox* google* dotnet* aspnetcore* powershell* adoptopenjdk* mysql* php* mongodb* moby* snap* || true
sudo -E apt-get -qq autoremove --purge
sudo rm -rf /usr/share/dotnet /opt/ghc /opt/hostedtoolcache/CodeQL /usr/local/lib/android
sudo apt-get clean
rm -rf ~/.cache/yarn ~/.npm
df -h
- name: Remove nbstore node_modules (darwin/linux)
if: ${{ inputs.platform != 'win32' }}
shell: bash
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
run: |
cargo clean
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
- name: Remove nbstore node_modules (windows)
if: ${{ inputs.platform == 'win32' }}
shell: bash
run: |
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite/affine/node_modules
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
- name: make
if: ${{ inputs.platform != 'win32' }}
run: yarn affine @affine/electron make --platform=${{ inputs.platform }} --arch=${{ inputs.arch }}
env:
SKIP_WEB_BUILD: 1
HOIST_NODE_MODULES: 1
NODE_OPTIONS: --max-old-space-size=14384
- name: package
if: ${{ inputs.platform == 'win32' }}
run: |
yarn affine @affine/electron package --platform=${{ inputs.platform }} --arch=${{ inputs.arch }}
env:
SKIP_WEB_BUILD: 1
HOIST_NODE_MODULES: 1
NODE_OPTIONS: --max-old-space-size=14384
- name: signing DMG
if: ${{ inputs.platform == 'darwin' && inputs.apple_codesign }}
run: |
codesign --force --sign "Developer ID Application: TOEVERYTHING PTE. LTD." packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make/AFFiNE.dmg
- name: Save artifacts (mac)
if: ${{ inputs.platform == 'darwin' }}
run: |
mkdir -p builds
mv packages/frontend/apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.dmg
mv packages/frontend/apps/electron/out/*/make/zip/darwin/${{ inputs.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.zip
- name: Save artifacts (linux)
if: ${{ inputs.platform == 'linux' }}
run: |
mkdir -p builds
mv packages/frontend/apps/electron/out/*/make/zip/linux/${{ inputs.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.zip
mv packages/frontend/apps/electron/out/*/make/*.AppImage ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage
mv packages/frontend/apps/electron/out/*/make/deb/${{ inputs.arch }}/*.deb ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.deb
mv packages/frontend/apps/electron/out/*/make/flatpak/*/*.flatpak ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.flatpak
- uses: actions/attest-build-provenance@v2
if: ${{ inputs.platform == 'darwin' }}
with:
subject-path: |
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.zip
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.dmg
- uses: actions/attest-build-provenance@v2
if: ${{ inputs.platform == 'linux' }}
with:
subject-path: |
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.zip
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.deb
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.flatpak
- name: Upload Artifact
if: ${{ inputs.platform == 'darwin' || inputs.platform == 'linux' }}
uses: actions/upload-artifact@v4
with:
name: affine-${{ inputs.platform }}-${{ inputs.arch }}-builds
path: builds
- name: get all files to be signed
id: get_files_to_be_signed
if: ${{ inputs.platform == 'win32' }}
shell: pwsh
run: |
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\apps\electron\out\', '') + '"' }) -join ' ')
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
echo $FILES_TO_BE_SIGNED
- name: Zip artifacts for faster upload
if: ${{ inputs.platform == 'win32' }}
shell: pwsh
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/apps/electron/out/* -DestinationPath archive.zip
- name: Save packaged artifacts for signing
if: ${{ inputs.platform == 'win32' }}
uses: actions/upload-artifact@v4
with:
name: packaged-${{ inputs.platform }}-${{ inputs.arch }}
path: |
archive.zip
!**/*.map

View File

@@ -1,32 +1,27 @@
name: Release Desktop name: Release Desktop App
on: on:
workflow_call: workflow_dispatch:
inputs: inputs:
build-type: build-type:
description: 'Build Type'
type: choice
required: true required: true
type: string default: canary
app-version: options:
- canary
- beta
- stable
is-draft:
description: 'Draft Release?'
type: boolean
required: true required: true
type: string default: true
git-short-hash: is-pre-release:
description: 'Pre Release? (labeled as "PreRelease")'
type: boolean
required: true required: true
type: string
desktop_macos:
description: 'Desktop - macOS'
required: false
default: true default: true
type: boolean
desktop_windows:
description: 'Desktop - Windows'
required: false
default: true
type: boolean
desktop_linux:
description: 'Desktop - Linux'
required: false
default: true
type: boolean
permissions: permissions:
actions: write actions: write
@@ -36,23 +31,22 @@ permissions:
attestations: write attestations: write
env: env:
BUILD_TYPE: ${{ inputs.build-type }} BUILD_TYPE: ${{ github.event.inputs.build-type }}
RELEASE_VERSION: ${{ inputs.app-version }}
DEBUG: 'affine:*,napi:*' DEBUG: 'affine:*,napi:*'
APP_NAME: affine APP_NAME: affine
MACOSX_DEPLOYMENT_TARGET: '11.6' MACOSX_DEPLOYMENT_TARGET: '10.13'
jobs: jobs:
before-make: before-make:
if: ${{ inputs.desktop_macos || inputs.desktop_windows || inputs.desktop_linux }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: ${{ inputs.build-type }} environment: ${{ github.event.inputs.build-type }}
outputs:
RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- name: Setup @sentry/cli - name: Setup @sentry/cli
@@ -64,17 +58,17 @@ jobs:
SENTRY_PROJECT: 'affine' SENTRY_PROJECT: 'affine'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_RELEASE: ${{ inputs.app-version }} SENTRY_RELEASE: ${{ steps.version.outputs.APP_VERSION }}
RELEASE_VERSION: ${{ inputs.app-version }} RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
- name: Upload web artifact - name: Upload web artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: desktop-web name: web
path: packages/frontend/apps/electron/resources/web-static path: packages/frontend/apps/electron/resources/web-static
make-distribution-macos: make-distribution:
if: ${{ inputs.desktop_macos }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -87,90 +81,221 @@ jobs:
platform: darwin platform: darwin
arch: arm64 arch: arm64
target: aarch64-apple-darwin target: aarch64-apple-darwin
needs: before-make
uses: ./.github/workflows/release-desktop-platform.yml
secrets: inherit
with:
build_type: ${{ inputs.build-type }}
app_version: ${{ inputs.app-version }}
git_short_hash: ${{ inputs.git-short-hash }}
runner: ${{ matrix.spec.runner }}
platform: ${{ matrix.spec.platform }}
arch: ${{ matrix.spec.arch }}
target: ${{ matrix.spec.target }}
apple_codesign: true
make-distribution-linux:
if: ${{ inputs.desktop_linux }}
strategy:
fail-fast: false
matrix:
spec:
- runner: ubuntu-latest - runner: ubuntu-latest
platform: linux platform: linux
arch: x64 arch: x64
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.spec.runner }}
needs: before-make needs: before-make
uses: ./.github/workflows/release-desktop-platform.yml environment: ${{ github.event.inputs.build-type }}
secrets: inherit env:
with: APPLE_ID: ${{ secrets.APPLE_ID }}
build_type: ${{ inputs.build-type }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
app_version: ${{ inputs.app-version }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
git_short_hash: ${{ inputs.git-short-hash }} SKIP_GENERATE_ASSETS: 1
runner: ${{ matrix.spec.runner }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
platform: ${{ matrix.spec.platform }} SENTRY_PROJECT: 'affine'
arch: ${{ matrix.spec.arch }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
target: ${{ matrix.spec.target }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
install_linux_deps: true SENTRY_RELEASE: ${{ needs.before-make.outputs.RELEASE_VERSION }}
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Setup Version
id: version
uses: ./.github/actions/setup-version
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
hard-link-nm: false
nmHoistingLimits: workspaces
enableScripts: false
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
target: ${{ matrix.spec.target }}
package: '@affine/native'
- uses: actions/download-artifact@v4
with:
name: web
path: packages/frontend/apps/electron/resources/web-static
package-distribution-windows-x64: - name: Build Desktop Layers
if: ${{ inputs.desktop_windows }} run: yarn affine @affine/electron build
needs: before-make
uses: ./.github/workflows/release-desktop-platform.yml
secrets: inherit
with:
build_type: ${{ inputs.build-type }}
app_version: ${{ inputs.app-version }}
git_short_hash: ${{ inputs.git-short-hash }}
runner: windows-latest
platform: win32
arch: x64
target: x86_64-pc-windows-msvc
enable_scripts: true
package-distribution-windows-arm64: - name: Signing By Apple Developer ID
if: ${{ inputs.desktop_windows }} if: ${{ matrix.spec.platform == 'darwin' }}
uses: apple-actions/import-codesign-certs@v5
with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
- name: Install additional dependencies on Linux
if: ${{ matrix.spec.platform == 'linux' }}
run: |
sudo add-apt-repository universe
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak update
# some flatpak deps need git protocol.file.allow
git config --global protocol.file.allow always
- name: Remove nbstore node_modules
shell: bash
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
run: |
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
- name: make
run: yarn affine @affine/electron make --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
env:
SKIP_WEB_BUILD: 1
HOIST_NODE_MODULES: 1
NODE_OPTIONS: --max-old-space-size=14384
- name: signing DMG
if: ${{ matrix.spec.platform == 'darwin' }}
run: |
codesign --force --sign "Developer ID Application: TOEVERYTHING PTE. LTD." packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make/AFFiNE.dmg
- name: Save artifacts (mac)
if: ${{ matrix.spec.platform == 'darwin' }}
run: |
mkdir -p builds
mv packages/frontend/apps/electron/out/*/make/*.dmg ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
mv packages/frontend/apps/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
- name: Save artifacts (linux)
if: ${{ matrix.spec.platform == 'linux' }}
run: |
mkdir -p builds
mv packages/frontend/apps/electron/out/*/make/zip/linux/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.zip
mv packages/frontend/apps/electron/out/*/make/*.AppImage ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.appimage
mv packages/frontend/apps/electron/out/*/make/deb/${{ matrix.spec.arch }}/*.deb ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.deb
mv packages/frontend/apps/electron/out/*/make/flatpak/*/*.flatpak ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.flatpak
- uses: actions/attest-build-provenance@v2
if: ${{ matrix.spec.platform == 'darwin' }}
with:
subject-path: |
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
- uses: actions/attest-build-provenance@v2
if: ${{ matrix.spec.platform == 'linux' }}
with:
subject-path: |
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.zip
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.appimage
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.deb
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: affine-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}-builds
path: builds
package-distribution-windows:
environment: ${{ github.event.inputs.build-type }}
strategy:
fail-fast: false
matrix:
spec:
- runner: windows-latest
platform: win32
arch: x64
target: x86_64-pc-windows-msvc
- runner: windows-latest
platform: win32
arch: arm64
target: aarch64-pc-windows-msvc
runs-on: ${{ matrix.spec.runner }}
needs: before-make needs: before-make
uses: ./.github/workflows/release-desktop-platform.yml outputs:
secrets: inherit FILES_TO_BE_SIGNED_x64: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED_x64 }}
with: FILES_TO_BE_SIGNED_arm64: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED_arm64 }}
build_type: ${{ inputs.build-type }} env:
app_version: ${{ inputs.app-version }} SKIP_GENERATE_ASSETS: 1
git_short_hash: ${{ inputs.git-short-hash }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
runner: windows-latest SENTRY_PROJECT: 'affine'
platform: win32 SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
arch: arm64 SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
target: aarch64-pc-windows-msvc SENTRY_RELEASE: ${{ needs.before-make.outputs.RELEASE_VERSION }}
enable_scripts: true MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Setup Version
id: version
uses: ./.github/actions/setup-version
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
hard-link-nm: false
nmHoistingLimits: workspaces
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
target: ${{ matrix.spec.target }}
package: '@affine/native'
- uses: actions/download-artifact@v4
with:
name: web
path: packages/frontend/apps/electron/resources/web-static
- name: Build Desktop Layers
run: yarn affine @affine/electron build
- name: Remove nbstore node_modules
shell: bash
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
run: |
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite/affine/node_modules
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
- name: package
run: |
yarn affine @affine/electron package --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
env:
SKIP_WEB_BUILD: 1
HOIST_NODE_MODULES: 1
NODE_OPTIONS: --max-old-space-size=14384
- name: get all files to be signed
id: get_files_to_be_signed
run: |
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\apps\electron\out\', '') + '"' }) -join ' ')
"FILES_TO_BE_SIGNED_${{ matrix.spec.arch }}=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
echo $FILES_TO_BE_SIGNED
- name: Zip artifacts for faster upload
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/apps/electron/out/* -DestinationPath archive.zip
- name: Save packaged artifacts for signing
uses: actions/upload-artifact@v4
with:
name: packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: |
archive.zip
!**/*.map
sign-packaged-artifacts-windows_x64: sign-packaged-artifacts-windows_x64:
if: ${{ inputs.desktop_windows }} needs: package-distribution-windows
needs: package-distribution-windows-x64
uses: ./.github/workflows/windows-signer.yml uses: ./.github/workflows/windows-signer.yml
with: with:
files: ${{ needs.package-distribution-windows-x64.outputs.files_to_be_signed }} files: ${{ needs.package-distribution-windows.outputs.FILES_TO_BE_SIGNED_x64 }}
artifact-name: packaged-win32-x64 artifact-name: packaged-win32-x64
sign-packaged-artifacts-windows_arm64: sign-packaged-artifacts-windows_arm64:
if: ${{ inputs.desktop_windows }} needs: package-distribution-windows
needs: package-distribution-windows-arm64
uses: ./.github/workflows/windows-signer.yml uses: ./.github/workflows/windows-signer.yml
with: with:
files: ${{ needs.package-distribution-windows-arm64.outputs.files_to_be_signed }} files: ${{ needs.package-distribution-windows.outputs.FILES_TO_BE_SIGNED_arm64 }}
artifact-name: packaged-win32-arm64 artifact-name: packaged-win32-arm64
make-windows-installer: make-windows-installer:
if: ${{ inputs.desktop_windows }}
needs: needs:
- sign-packaged-artifacts-windows_x64 - sign-packaged-artifacts-windows_x64
- sign-packaged-artifacts-windows_arm64 - sign-packaged-artifacts-windows_arm64
@@ -189,9 +314,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js - name: Setup Node.js
timeout-minutes: 10 timeout-minutes: 10
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
@@ -201,44 +325,13 @@ jobs:
nmHoistingLimits: workspaces nmHoistingLimits: workspaces
env: env:
npm_config_arch: ${{ matrix.spec.arch }} npm_config_arch: ${{ matrix.spec.arch }}
- name: Download packaged artifacts - name: Download and overwrite packaged artifacts
uses: actions/download-artifact@v4
with:
name: packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: packaged-unsigned
- name: unzip packaged artifacts
run: Expand-Archive -Path packaged-unsigned/archive.zip -DestinationPath packages/frontend/apps/electron/out
- name: Download signed packaged file diff
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: signed-packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }} name: signed-packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: signed-packaged-diff path: .
- name: Apply signed packaged file diff - name: unzip file
shell: pwsh run: Expand-Archive -Path signed.zip -DestinationPath packages/frontend/apps/electron/out
run: |
$DiffRoot = 'signed-packaged-diff/files'
$TargetRoot = 'packages/frontend/apps/electron/out'
if (!(Test-Path -LiteralPath $DiffRoot)) {
throw "Signed diff directory not found: $DiffRoot"
}
Copy-Item -Path (Join-Path $DiffRoot '*') -Destination $TargetRoot -Recurse -Force
$ManifestPath = 'signed-packaged-diff/manifest.json'
if (Test-Path -LiteralPath $ManifestPath) {
$ManifestEntries = @(Get-Content -LiteralPath $ManifestPath | ConvertFrom-Json)
foreach ($Entry in $ManifestEntries) {
$TargetPath = Join-Path $TargetRoot $Entry.path
if (!(Test-Path -LiteralPath $TargetPath -PathType Leaf)) {
throw "Applied signed file not found: $($Entry.path)"
}
$TargetHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $TargetPath).Hash
if ($TargetHash -ne $Entry.sha256) {
throw "Signed file hash mismatch: $($Entry.path)"
}
}
}
- name: Make squirrel.windows installer - name: Make squirrel.windows installer
run: yarn affine @affine/electron make-squirrel --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }} run: yarn affine @affine/electron make-squirrel --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
@@ -263,7 +356,6 @@ jobs:
path: archive.zip path: archive.zip
sign-installer-artifacts-windows-x64: sign-installer-artifacts-windows-x64:
if: ${{ inputs.desktop_windows }}
needs: make-windows-installer needs: make-windows-installer
uses: ./.github/workflows/windows-signer.yml uses: ./.github/workflows/windows-signer.yml
with: with:
@@ -271,7 +363,6 @@ jobs:
artifact-name: installer-win32-x64 artifact-name: installer-win32-x64
sign-installer-artifacts-windows-arm64: sign-installer-artifacts-windows-arm64:
if: ${{ inputs.desktop_windows }}
needs: make-windows-installer needs: make-windows-installer
uses: ./.github/workflows/windows-signer.yml uses: ./.github/workflows/windows-signer.yml
with: with:
@@ -279,7 +370,6 @@ jobs:
artifact-name: installer-win32-arm64 artifact-name: installer-win32-arm64
finalize-installer-windows: finalize-installer-windows:
if: ${{ inputs.desktop_windows }}
needs: needs:
[ [
sign-installer-artifacts-windows-x64, sign-installer-artifacts-windows-x64,
@@ -298,58 +388,27 @@ jobs:
arch: arm64 arch: arm64
runs-on: ${{ matrix.spec.runner }} runs-on: ${{ matrix.spec.runner }}
steps: steps:
- name: Download installer artifacts - name: Download and overwrite installer artifacts
uses: actions/download-artifact@v4
with:
name: installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: installer-unsigned
- name: unzip installer artifacts
run: Expand-Archive -Path installer-unsigned/archive.zip -DestinationPath packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make
- name: Download signed installer file diff
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: signed-installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }} name: signed-installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: signed-installer-diff path: .
- name: Apply signed installer file diff - name: unzip file
shell: pwsh run: Expand-Archive -Path signed.zip -DestinationPath packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make
run: |
$DiffRoot = 'signed-installer-diff/files'
$TargetRoot = 'packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make'
if (!(Test-Path -LiteralPath $DiffRoot)) {
throw "Signed diff directory not found: $DiffRoot"
}
Copy-Item -Path (Join-Path $DiffRoot '*') -Destination $TargetRoot -Recurse -Force
$ManifestPath = 'signed-installer-diff/manifest.json'
if (Test-Path -LiteralPath $ManifestPath) {
$ManifestEntries = @(Get-Content -LiteralPath $ManifestPath | ConvertFrom-Json)
foreach ($Entry in $ManifestEntries) {
$TargetPath = Join-Path $TargetRoot $Entry.path
if (!(Test-Path -LiteralPath $TargetPath -PathType Leaf)) {
throw "Applied signed file not found: $($Entry.path)"
}
$TargetHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $TargetPath).Hash
if ($TargetHash -ne $Entry.sha256) {
throw "Signed file hash mismatch: $($Entry.path)"
}
}
}
- name: Save artifacts - name: Save artifacts
run: | run: |
mkdir -p builds mkdir -p builds
mv packages/frontend/apps/electron/out/*/make/zip/win32/${{ matrix.spec.arch }}/AFFiNE*-win32-${{ matrix.spec.arch }}-*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip mv packages/frontend/apps/electron/out/*/make/zip/win32/${{ matrix.spec.arch }}/AFFiNE*-win32-${{ matrix.spec.arch }}-*.zip ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
mv packages/frontend/apps/electron/out/*/make/squirrel.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.exe mv packages/frontend/apps/electron/out/*/make/squirrel.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.exe
mv packages/frontend/apps/electron/out/*/make/nsis.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe mv packages/frontend/apps/electron/out/*/make/nsis.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe
- uses: actions/attest-build-provenance@v2 - uses: actions/attest-build-provenance@v2
with: with:
subject-path: | subject-path: |
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.exe ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.exe
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -358,18 +417,17 @@ jobs:
path: builds path: builds
release: release:
if: ${{ inputs.desktop_macos && inputs.desktop_linux && inputs.desktop_windows }} needs: [before-make, make-distribution, finalize-installer-windows]
needs:
[
before-make,
make-distribution-macos,
make-distribution-linux,
finalize-installer-windows,
]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: web
path: web-static
- name: Zip web-static
run: zip -r web-static.zip web-static
- name: Download Artifacts (macos-x64) - name: Download Artifacts (macos-x64)
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
@@ -407,12 +465,32 @@ jobs:
run: | run: |
node ./scripts/generate-release-yml.mjs node ./scripts/generate-release-yml.mjs
env: env:
RELEASE_VERSION: ${{ env.RELEASE_VERSION }} RELEASE_VERSION: ${{ needs.before-make.outputs.RELEASE_VERSION }}
- name: Create GitHub Release - name: Create Release Draft
if: ${{ github.ref_type == 'tag' }}
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
name: ${{ env.RELEASE_VERSION }} name: ${{ needs.before-make.outputs.RELEASE_VERSION }}
draft: ${{ inputs.build-type == 'stable' }} body: ''
prerelease: ${{ inputs.build-type != 'stable' }} draft: ${{ github.event.inputs.is-draft }}
tag_name: v${{ env.RELEASE_VERSION}} prerelease: ${{ github.event.inputs.is-pre-release }}
files: ./release/* files: |
./release/*
./release/.env.example
- name: Create Nightly Release Draft
if: ${{ github.ref_type == 'branch' }}
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
with:
# Temporarily, treat release from branch as nightly release, artifact saved to AFFiNE-Releases.
# Need to improve internal build and nightly release logic.
repository: 'toeverything/AFFiNE-Releases'
name: ${{ needs.before-make.outputs.RELEASE_VERSION }}
tag_name: ${{ needs.before-make.outputs.RELEASE_VERSION }}
body: ''
draft: false
prerelease: true
files: |
./release/*
./release/.env.example

View File

@@ -1,36 +1,68 @@
name: Release Mobile name: Release Mobile App
on: on:
workflow_call: workflow_call:
inputs: inputs:
app-version: build-target:
type: string description: 'Build Target'
required: true
git-short-hash:
type: string type: string
required: true required: true
build-type: build-type:
description: 'Build Type'
type: string type: string
required: true required: true
ios-app-version: workflow_dispatch:
type: string inputs:
required: false build-target:
description: 'Build Target'
type: choice
required: true
default: distribution
options:
- development
- distribution
build-type:
description: 'Build Type'
type: choice
required: true
default: canary
options:
- canary
- beta
- stable
env: env:
BUILD_TYPE: ${{ inputs.build-type }} BUILD_TYPE: ${{ inputs.build-type || github.event.inputs.build-type }}
BUILD_TARGET: ${{ inputs.build-target || github.event.inputs.build-target }}
DEBUG: napi:* DEBUG: napi:*
KEYCHAIN_NAME: ${{ github.workspace }}/signing_temp KEYCHAIN_NAME: ${{ github.workspace }}/signing_temp
jobs: jobs:
build-ios-web: output-env:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: ${{ inputs.build-type }} outputs:
ENVIRONMENT: ${{ steps.env.outputs.ENVIRONMENT }}
steps:
- name: Output Environment
id: env
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "ENVIRONMENT=${{ github.event.inputs.build-type }}" >> $GITHUB_OUTPUT
else
echo "ENVIRONMENT=" >> $GITHUB_OUTPUT
fi
build-ios-web:
needs:
- output-env
runs-on: ubuntu-24.04-arm
environment: ${{ needs.output-env.outputs.ENVIRONMENT }}
outputs:
RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- name: Setup @sentry/cli - name: Setup @sentry/cli
@@ -39,12 +71,13 @@ jobs:
run: yarn affine @affine/ios build run: yarn affine @affine/ios build
env: env:
PUBLIC_PATH: '/' PUBLIC_PATH: '/'
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine' SENTRY_PROJECT: 'affine'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_RELEASE: ${{ inputs.app-version }} SENTRY_RELEASE: ${{ steps.version.outputs.APP_VERSION }}
RELEASE_VERSION: ${{ inputs.app-version }} RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
- name: Upload ios artifact - name: Upload ios artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -52,13 +85,17 @@ jobs:
path: packages/frontend/apps/ios/dist path: packages/frontend/apps/ios/dist
build-android-web: build-android-web:
runs-on: ubuntu-latest runs-on: ubuntu-24.04-arm
needs:
- output-env
environment: ${{ needs.output-env.outputs.ENVIRONMENT }}
outputs:
RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- name: Setup @sentry/cli - name: Setup @sentry/cli
@@ -67,31 +104,46 @@ jobs:
run: yarn affine @affine/android build run: yarn affine @affine/android build
env: env:
PUBLIC_PATH: '/' PUBLIC_PATH: '/'
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine' SENTRY_PROJECT: 'affine'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_RELEASE: ${{ inputs.app-version }} SENTRY_RELEASE: ${{ steps.version.outputs.APP_VERSION }}
RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
- name: Upload android artifact - name: Upload android artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: android name: android
path: packages/frontend/apps/android/dist path: packages/frontend/apps/android/dist
ios: determine-ios-runner:
runs-on: 'macos-15' runs-on: ubuntu-latest
needs: needs:
- build-ios-web - build-ios-web
outputs:
RUNNER: ${{ steps.runner.outputs.RUNNER }}
steps:
- name: Determine Runner
id: runner
# Randomly pick runner with 80% chance for blaze/macos-14 and 20% chance for namespace-profile-macos
# blaze/macos-14 is free but has limited concurrency
run: |
RANDOM_NUMBER=$(( $RANDOM % 100 + 1 ))
if [ $RANDOM_NUMBER -le 20 ]; then
echo "Selected namespace-profile-macos (20% probability)"
echo "RUNNER=namespace-profile-macos" >> $GITHUB_OUTPUT
else
echo "Selected blaze/macos-14 (80% probability)"
echo "RUNNER=blaze/macos-14" >> $GITHUB_OUTPUT
fi
ios:
runs-on: ${{ github.ref_name == 'canary' && 'macos-latest' || needs.determine-ios-runner.outputs.RUNNER }}
needs:
- determine-ios-runner
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version
uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
ios-app-version: ${{ inputs.ios-app-version }}
- name: 'Update Code Sign Identity'
shell: bash
run: ./packages/frontend/apps/ios/update_code_sign_identity.sh
- name: Download mobile artifact - name: Download mobile artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
@@ -108,7 +160,7 @@ jobs:
enableScripts: false enableScripts: false
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: 26.2 xcode-version: 16.2
- name: Install Swiftformat - name: Install Swiftformat
run: brew install swiftformat run: brew install swiftformat
- name: Cap sync - name: Cap sync
@@ -126,6 +178,7 @@ jobs:
package: 'affine_mobile_native' package: 'affine_mobile_native'
no-build: 'true' no-build: 'true'
- name: Testflight - name: Testflight
if: ${{ env.BUILD_TYPE != 'stable' }}
working-directory: packages/frontend/apps/ios/App working-directory: packages/frontend/apps/ios/App
run: | run: |
echo -n "${{ env.BUILD_PROVISION_PROFILE }}" | base64 --decode -o $PP_PATH echo -n "${{ env.BUILD_PROVISION_PROFILE }}" | base64 --decode -o $PP_PATH
@@ -133,7 +186,6 @@ jobs:
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
fastlane beta fastlane beta
env: env:
BUILD_TARGET: distribution
BUILD_PROVISION_PROFILE: ${{ secrets.BUILD_PROVISION_PROFILE }} BUILD_PROVISION_PROFILE: ${{ secrets.BUILD_PROVISION_PROFILE }}
PP_PATH: ${{ runner.temp }}/build_pp.mobileprovision PP_PATH: ${{ runner.temp }}/build_pp.mobileprovision
APPLE_STORE_CONNECT_API_KEY_ID: ${{ secrets.APPLE_STORE_CONNECT_API_KEY_ID }} APPLE_STORE_CONNECT_API_KEY_ID: ${{ secrets.APPLE_STORE_CONNECT_API_KEY_ID }}
@@ -149,9 +201,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version - name: Setup Version
id: version
uses: ./.github/actions/setup-version uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Download mobile artifact - name: Download mobile artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
@@ -184,6 +235,7 @@ jobs:
- name: Auth gcloud - name: Auth gcloud
id: auth id: auth
uses: google-github-actions/auth@v2 uses: google-github-actions/auth@v2
if: ${{ env.BUILD_TARGET == 'distribution' }}
with: with:
workload_identity_provider: 'projects/${{ secrets.GCP_PROJECT_NUMBER }}/locations/global/workloadIdentityPools/github-actions/providers/github-actions-helm-deploy' workload_identity_provider: 'projects/${{ secrets.GCP_PROJECT_NUMBER }}/locations/global/workloadIdentityPools/github-actions/providers/github-actions-helm-deploy'
service_account: '${{ secrets.GCP_HELM_DEPLOY_SERVICE_ACCOUNT }}' service_account: '${{ secrets.GCP_HELM_DEPLOY_SERVICE_ACCOUNT }}'
@@ -197,6 +249,7 @@ jobs:
cache: 'gradle' cache: 'gradle'
- name: Auto increment version code - name: Auto increment version code
id: bump id: bump
if: ${{ env.BUILD_TARGET == 'distribution' }}
run: yarn affine @affine/playstore-auto-bump bump run: yarn affine @affine/playstore-auto-bump bump
env: env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.auth.outputs.credentials_file_path }} GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.auth.outputs.credentials_file_path }}
@@ -208,13 +261,14 @@ jobs:
AFFINE_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_PASSWORD }} AFFINE_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_PASSWORD }}
AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD }} AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD }}
AFFINE_ANDROID_SIGN_KEYSTORE: ${{ secrets.AFFINE_ANDROID_SIGN_KEYSTORE }} AFFINE_ANDROID_SIGN_KEYSTORE: ${{ secrets.AFFINE_ANDROID_SIGN_KEYSTORE }}
VERSION_NAME: ${{ inputs.app-version }} VERSION_NAME: ${{ steps.version.outputs.APP_VERSION }}
- name: Upload to Google Play - name: Upload to Google Play
uses: r0adkll/upload-google-play@v1 uses: r0adkll/upload-google-play@v1
if: ${{ env.BUILD_TARGET == 'distribution' }}
with: with:
serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }} serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }}
packageName: app.affine.pro packageName: app.affine.pro
releaseName: ${{ inputs.app-version }} releaseName: ${{ steps.version.outputs.APP_VERSION }}
releaseFiles: packages/frontend/apps/android/App/app/build/outputs/bundle/${{ env.BUILD_TYPE }}Release/app-${{ env.BUILD_TYPE }}-release-signed.aab releaseFiles: packages/frontend/apps/android/App/app/build/outputs/bundle/${{ env.BUILD_TYPE }}Release/app-${{ env.BUILD_TYPE }}-release-signed.aab
track: internal track: internal
status: draft status: draft

View File

@@ -1,210 +0,0 @@
name: Release
on:
schedule:
- cron: '0 9 * * *'
workflow_dispatch:
inputs:
web:
description: 'Release Web?'
required: true
type: boolean
default: false
desktop_macos:
description: 'Desktop - macOS'
required: true
type: boolean
default: false
desktop_windows:
description: 'Desktop - Windows'
required: true
type: boolean
default: false
desktop_linux:
description: 'Desktop - Linux'
required: true
type: boolean
default: false
mobile:
description: 'Release Mobile?'
required: true
type: boolean
default: false
ios-app-version:
description: 'iOS App Store Version (Optional, use tag version if empty)'
required: false
type: string
permissions:
contents: write
pull-requests: write
actions: write
id-token: write
packages: write
security-events: write
attestations: write
issues: write
jobs:
prepare:
name: Prepare
runs-on: ubuntu-latest
outputs:
APP_VERSION: ${{ steps.prepare.outputs.APP_VERSION }}
GIT_SHORT_HASH: ${{ steps.prepare.outputs.GIT_SHORT_HASH }}
BUILD_TYPE: ${{ steps.prepare.outputs.BUILD_TYPE }}
steps:
- uses: actions/checkout@v4
- name: Prepare Release
id: prepare
uses: ./.github/actions/prepare-release
canary-gate:
name: Canary Gate
runs-on: ubuntu-latest
needs:
- prepare
outputs:
SHOULD_RELEASE: ${{ steps.decide.outputs.SHOULD_RELEASE }}
LAST_CANARY_TAG: ${{ steps.decide.outputs.LAST_CANARY_TAG }}
LAST_CANARY_SHA: ${{ steps.decide.outputs.LAST_CANARY_SHA }}
steps:
- name: Decide whether to release
id: decide
uses: actions/github-script@v7
with:
script: |
const buildType = '${{ needs.prepare.outputs.BUILD_TYPE }}'
if (buildType !== 'canary') {
core.setOutput('SHOULD_RELEASE', 'true')
return
}
const owner = context.repo.owner
const repo = context.repo.repo
const currentSha = context.sha
const canaryTagRe = /^v\d+\.\d+\.\d+-canary\.[0-9a-f]+$/i
let page = 1
const perPage = 100
let lastCanary = null
while (!lastCanary && page <= 10) {
const { data } = await github.rest.repos.listTags({
owner,
repo,
per_page: perPage,
page,
})
for (const tag of data) {
if (canaryTagRe.test(tag.name)) {
lastCanary = tag
break
}
}
if (data.length < perPage) break
page++
}
if (!lastCanary) {
core.warning('No canary tags found; proceeding with canary release.')
core.setOutput('SHOULD_RELEASE', 'true')
return
}
core.setOutput('LAST_CANARY_TAG', lastCanary.name)
core.setOutput('LAST_CANARY_SHA', lastCanary.commit.sha)
const shouldRelease = lastCanary.commit.sha !== currentSha
core.info(`Latest canary tag ${lastCanary.name} -> ${lastCanary.commit.sha}; current ${currentSha}; should_release=${shouldRelease}`)
core.setOutput('SHOULD_RELEASE', shouldRelease ? 'true' : 'false')
cloud:
name: Release Cloud
if: ${{ inputs.web || github.event_name != 'workflow_dispatch' }}
needs:
- prepare
uses: ./.github/workflows/release-cloud.yml
secrets: inherit
with:
build-type: ${{ needs.prepare.outputs.BUILD_TYPE }}
app-version: ${{ needs.prepare.outputs.APP_VERSION }}
git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }}
image:
name: Release Docker Image
if: ${{ needs.canary-gate.outputs.SHOULD_RELEASE == 'true' }}
runs-on: ubuntu-latest
needs:
- prepare
- canary-gate
- cloud
steps:
- uses: trstringer/manual-approval@v1
if: ${{ needs.prepare.outputs.BUILD_TYPE == 'stable' }}
name: Wait for approval
with:
secret: ${{ secrets.GITHUB_TOKEN }}
approvers: darkskygit
minimum-approvals: 1
fail-on-denial: true
issue-title: Please confirm to release docker image
issue-body: |
Env: ${{ needs.prepare.outputs.BUILD_TYPE }}
Candidate: ghcr.io/toeverything/affine:${{ needs.prepare.outputs.BUILD_TYPE }}-${{ needs.prepare.outputs.GIT_SHORT_HASH }}
Tag: ghcr.io/toeverything/affine:${{ needs.prepare.outputs.BUILD_TYPE }}
> comment with "approve", "approved", "lgtm", "yes" to approve
> comment with "deny", "denied", "no" to deny
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
logout: false
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Tag Image
run: |
docker buildx imagetools create --tag ghcr.io/toeverything/affine:${{needs.prepare.outputs.BUILD_TYPE}} ghcr.io/toeverything/affine:${{needs.prepare.outputs.BUILD_TYPE}}-${{needs.prepare.outputs.GIT_SHORT_HASH}}
docker buildx imagetools create --tag ghcr.io/toeverything/affine:${{needs.prepare.outputs.APP_VERSION}} ghcr.io/toeverything/affine:${{needs.prepare.outputs.BUILD_TYPE}}-${{needs.prepare.outputs.GIT_SHORT_HASH}}
desktop:
name: Release Desktop
if: >-
${{
(github.event_name != 'workflow_dispatch' && needs.canary-gate.outputs.SHOULD_RELEASE == 'true') ||
inputs.desktop_macos ||
inputs.desktop_windows ||
inputs.desktop_linux
}}
needs:
- prepare
- canary-gate
uses: ./.github/workflows/release-desktop.yml
secrets: inherit
with:
build-type: ${{ needs.prepare.outputs.BUILD_TYPE }}
app-version: ${{ needs.prepare.outputs.APP_VERSION }}
git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }}
desktop_macos: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_macos }}
desktop_windows: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_windows }}
desktop_linux: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_linux }}
mobile:
name: Release Mobile
if: ${{ inputs.mobile }}
needs:
- prepare
uses: ./.github/workflows/release-mobile.yml
secrets: inherit
with:
build-type: ${{ needs.prepare.outputs.BUILD_TYPE }}
app-version: ${{ needs.prepare.outputs.APP_VERSION }}
git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }}
ios-app-version: ${{ inputs.ios-app-version }}

72
.github/workflows/sync-i18n.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: Sync I18n with Crowdin
on:
push:
branches:
- canary
paths:
- 'packages/frontend/i18n/**'
workflow_dispatch:
jobs:
synchronize-with-crowdin:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Crowdin action
id: crowdin
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: false
download_translations: true
auto_approve_imported: true
import_eq_suggestions: true
export_only_approved: true
skip_untranslated_strings: true
localization_branch_name: l10n_crowdin_translations
create_pull_request: true
pull_request_title: 'chore(i18n): sync translations'
pull_request_body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action)'
pull_request_base_branch_name: 'canary'
config: packages/frontend/i18n/crowdin.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
i18n-codegen:
needs: synchronize-with-crowdin
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: l10n_crowdin_translations
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
full-cache: true
- name: Run i18n codegen
run: yarn affine @affine/i18n build
- name: Commit changes
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "chore(i18n): i18n codegen"
git push origin l10n_crowdin_translations

View File

@@ -29,44 +29,14 @@ jobs:
shell: cmd shell: cmd
run: | run: |
cd ${{ env.ARCHIVE_DIR }}/out cd ${{ env.ARCHIVE_DIR }}/out
signtool sign /tr http://timestamp.globalsign.com/tsa/r6advanced1 /td sha256 /fd sha256 /a ${{ inputs.files }} signtool sign /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /a ${{ inputs.files }}
- name: collect signed file diff - name: zip file
shell: powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File {0} shell: cmd
run: | run: |
$OutDir = Join-Path '${{ env.ARCHIVE_DIR }}' 'out' cd ${{ env.ARCHIVE_DIR }}
$DiffDir = Join-Path '${{ env.ARCHIVE_DIR }}' 'signed-diff' 7za a signed.zip .\out\*
$FilesDir = Join-Path $DiffDir 'files'
New-Item -ItemType Directory -Path $FilesDir -Force | Out-Null
$SignedFiles = [regex]::Matches('${{ inputs.files }}', '"([^"]+)"') | ForEach-Object { $_.Groups[1].Value }
if ($SignedFiles.Count -eq 0) {
throw 'No files to sign were provided.'
}
$Manifest = @()
foreach ($RelativePath in $SignedFiles) {
$SourcePath = Join-Path $OutDir $RelativePath
if (!(Test-Path -LiteralPath $SourcePath -PathType Leaf)) {
throw "Signed file not found: $RelativePath"
}
$TargetPath = Join-Path $FilesDir $RelativePath
$TargetDir = Split-Path -Parent $TargetPath
if ($TargetDir) {
New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null
}
Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force
$Manifest += [PSCustomObject]@{
path = $RelativePath
sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $TargetPath).Hash
}
}
$Manifest | ConvertTo-Json -Depth 4 | Out-File -FilePath (Join-Path $DiffDir 'manifest.json') -Encoding utf8
Write-Host "Collected $($SignedFiles.Count) signed files."
- name: upload - name: upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: signed-${{ inputs.artifact-name }} name: signed-${{ inputs.artifact-name }}
path: ${{ env.ARCHIVE_DIR }}/signed-diff path: ${{ env.ARCHIVE_DIR }}/signed.zip

4
.gitignore vendored
View File

@@ -33,9 +33,6 @@ node_modules
!.vscode/launch.template.json !.vscode/launch.template.json
!.vscode/extensions.json !.vscode/extensions.json
# Kiro
.kiro
# misc # misc
/.sass-cache /.sass-cache
/connect.lock /connect.lock
@@ -47,7 +44,6 @@ testem.log
.pnpm-debug.log .pnpm-debug.log
/typings /typings
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
.context
# System Files # System Files
.DS_Store .DS_Store

2
.nvmrc
View File

@@ -1 +1 @@
22.22.0 22.16.0

View File

@@ -2,7 +2,6 @@
**/node_modules **/node_modules
.yarn .yarn
.github/helm .github/helm
.git
.vscode .vscode
.yarnrc.yml .yarnrc.yml
.docker .docker

View File

@@ -1,11 +1,7 @@
exclude = [ include = ["./*.toml", "./packages/**/*.toml"]
"node_modules/**/*.toml",
"target/**/*.toml",
"packages/frontend/apps/ios/App/Packages/AffineGraphQL/**/*.toml",
]
# https://taplo.tamasfe.dev/configuration/formatter-options.html
[formatting] [formatting]
align_entries = true align_entries = true
indent_tables = true column_width = 180
reorder_keys = true reorder_arrays = true
reorder_keys = true

View File

@@ -1,5 +1,5 @@
{ {
"prisma.pinToPrisma6": true, "eslint.packageManager": "yarn",
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.formatOnSaveMode": "file", "editor.formatOnSaveMode": "file",
@@ -14,13 +14,11 @@
"testid", "testid",
"schemars" "schemars"
], ],
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": { "explorer.fileNesting.patterns": {
"*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts, ${capture}.d.ts.map", "*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts, ${capture}.d.ts.map",
"package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, eslint.*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.*, webpack*, workspace.json, xo.config.*, yarn*, babel.*, .babelrc, project.json, .oxlintrc.json, oxlint.json, nyc.config.*", "package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, cypress.*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.config.*, webpack*, workspace.json, xo.config.*, yarn*, babel.*, .babelrc, project.json",
"Cargo.toml": "Cargo.lock, rust-toolchain*, rustfmt.toml, .taplo.toml", "Cargo.toml": "Cargo.lock",
"README.md": "LICENSE*, CHANGELOG.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md, SECURITY.md, README.*", "README.md": "LICENSE, CHANGELOG.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md"
".gitignore": ".gitattributes, .dockerignore, .eslintignore, .prettierignore, .stylelintignore, .tslintignore, .yarnignore"
}, },
"[rust]": { "[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer" "editor.defaultFormatter": "rust-lang.rust-analyzer"
@@ -34,6 +32,5 @@
"vitest.include": ["packages/**/*.spec.ts", "packages/**/*.spec.tsx"], "vitest.include": ["packages/**/*.spec.ts", "packages/**/*.spec.tsx"],
"rust-analyzer.check.extraEnv": { "rust-analyzer.check.extraEnv": {
"DATABASE_URL": "sqlite:affine.db" "DATABASE_URL": "sqlite:affine.db"
}, }
"typescript.tsdk": "node_modules/typescript/lib"
} }

File diff suppressed because one or more lines are too long

View File

@@ -12,4 +12,4 @@ npmPublishAccess: public
npmRegistryServer: "https://registry.npmjs.org" npmRegistryServer: "https://registry.npmjs.org"
yarnPath: .yarn/releases/yarn-4.12.0.cjs yarnPath: .yarn/releases/yarn-4.9.1.cjs

2744
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ members = [
"./packages/backend/native", "./packages/backend/native",
"./packages/common/native", "./packages/common/native",
"./packages/common/y-octo/core", "./packages/common/y-octo/core",
"./packages/common/y-octo/node",
"./packages/common/y-octo/utils", "./packages/common/y-octo/utils",
"./packages/frontend/mobile-native", "./packages/frontend/mobile-native",
"./packages/frontend/native", "./packages/frontend/native",
@@ -12,125 +13,93 @@ members = [
] ]
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]
edition = "2024" edition = "2024"
[workspace.dependencies] [workspace.dependencies]
affine_common = { path = "./packages/common/native" } affine_common = { path = "./packages/common/native" }
affine_nbstore = { path = "./packages/frontend/native/nbstore" } affine_nbstore = { path = "./packages/frontend/native/nbstore" }
ahash = "0.8" ahash = "0.8"
anyhow = "1" anyhow = "1"
arbitrary = { version = "1.3", features = ["derive"] } arbitrary = { version = "1.3", features = ["derive"] }
assert-json-diff = "2.0" assert-json-diff = "2.0"
async-lock = { version = "3.4.0", features = ["loom"] } async-lock = { version = "3.4.0", features = ["loom"] }
base64-simd = "0.8" base64-simd = "0.8"
bitvec = "1.0" bitvec = "1.0"
block2 = "0.6" block2 = "0.6"
byteorder = "1.5" byteorder = "1.5"
chrono = "0.4" chrono = "0.4"
clap = { version = "4.4", features = ["derive"] } clap = { version = "4.4", features = ["derive"] }
core-foundation = "0.10" core-foundation = "0.10"
coreaudio-rs = "0.12" coreaudio-rs = "0.12"
cpal = "0.15" criterion = { version = "0.5", features = ["html_reports"] }
criterion = { version = "0.5", features = ["html_reports"] } criterion2 = { version = "3", default-features = false }
criterion2 = { version = "3", default-features = false } dispatch2 = "0.3"
crossbeam-channel = "0.5" docx-parser = { git = "https://github.com/toeverything/docx-parser" }
dispatch2 = "0.3" dotenvy = "0.15"
docx-parser = { git = "https://github.com/toeverything/docx-parser" } file-format = { version = "0.26", features = ["reader"] }
dotenvy = "0.15" homedir = "0.3"
file-format = { version = "0.28", features = ["reader"] } infer = { version = "0.19.0" }
homedir = "0.3" lasso = { version = "0.7", features = ["multi-threaded"] }
infer = { version = "0.19.0" } lib0 = { version = "0.16", features = ["lib0-serde"] }
lasso = { version = "0.7", features = ["multi-threaded"] } libc = "0.2"
lib0 = { version = "0.16", features = ["lib0-serde"] } log = "0.4"
libc = "0.2" loom = { version = "0.7", features = ["checkpoint"] }
log = "0.4" mimalloc = "0.1"
loom = { version = "0.7", features = ["checkpoint"] } nanoid = "0.4"
lru = "0.16" napi = { version = "3.0.0-alpha.31", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
memory-indexer = "0.3.0" napi-build = { version = "2" }
mimalloc = "0.1" napi-derive = { version = "3.0.0-alpha.28" }
mp4parse = "0.17" nom = "8"
nanoid = "0.4" notify = { version = "8", features = ["serde"] }
napi = { version = "3.7.0", features = [ objc2 = "0.6"
"async", objc2-foundation = "0.3"
"chrono_date", once_cell = "1"
"error_anyhow", ordered-float = "5"
"napi9", parking_lot = "0.12"
"serde", path-ext = "0.1.2"
] } pdf-extract = { git = "https://github.com/toeverything/pdf-extract", branch = "darksky/improve-font-decoding" }
napi-build = { version = "2" } phf = { version = "0.11", features = ["macros"] }
napi-derive = { version = "3.4" } proptest = "1.3"
nom = "8" proptest-derive = "0.5"
notify = { version = "8", features = ["serde"] } rand = "0.9"
objc2 = "0.6" rand_chacha = "0.9"
objc2-foundation = "0.3" rand_distr = "0.5"
once_cell = "1" rayon = "1.10"
ordered-float = "5" readability = { version = "0.3.0", default-features = false }
parking_lot = "0.12" regex = "1.10"
path-ext = "0.1.2" rubato = "0.16"
pdf-extract = { git = "https://github.com/toeverything/pdf-extract", branch = "darksky/improve-font-decoding" } screencapturekit = "0.3"
phf = { version = "0.11", features = ["macros"] } serde = "1"
proptest = "1.3" serde_json = "1"
proptest-derive = "0.5" sha3 = "0.10"
pulldown-cmark = "0.13" smol_str = "0.3"
rand = "0.9" sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "tls-rustls"] }
rand_chacha = "0.9" strum_macros = "0.27.0"
rand_distr = "0.5" symphonia = { version = "0.5", features = ["all", "opt-simd"] }
rayon = "1.10" text-splitter = "0.25"
readability = { version = "0.3.0", default-features = false } thiserror = "2"
regex = "1.10" tiktoken-rs = "0.6"
rubato = "0.16" tokio = "1.37"
screencapturekit = "0.3" tree-sitter = { version = "0.25" }
serde = "1" tree-sitter-c = { version = "0.23" }
serde_json = "1" tree-sitter-c-sharp = { version = "0.23" }
sha3 = "0.10" tree-sitter-cpp = { version = "0.23" }
smol_str = "0.3" tree-sitter-go = { version = "0.23" }
sqlx = { version = "0.8", default-features = false, features = [ tree-sitter-java = { version = "0.23" }
"chrono", tree-sitter-javascript = { version = "0.23" }
"macros", tree-sitter-kotlin-ng = { version = "1.1" }
"migrate", tree-sitter-python = { version = "0.23" }
"runtime-tokio", tree-sitter-rust = { version = "0.24" }
"sqlite", tree-sitter-scala = { version = "0.23" }
"tls-rustls", tree-sitter-typescript = { version = "0.23" }
] } uniffi = "0.29"
strum_macros = "0.27.0" url = { version = "2.5" }
symphonia = { version = "0.5", features = ["all", "opt-simd"] } uuid = "1.8"
text-splitter = "0.27" v_htmlescape = "0.15"
thiserror = "2" y-octo = { path = "./packages/common/y-octo/core" }
tiktoken-rs = "0.7" y-sync = { version = "0.4" }
tokio = "1.45" yrs = "0.23.0"
tree-sitter = { version = "0.25" }
tree-sitter-c = { version = "0.24" }
tree-sitter-c-sharp = { version = "0.23" }
tree-sitter-cpp = { version = "0.23" }
tree-sitter-go = { version = "0.23" }
tree-sitter-java = { version = "0.23" }
tree-sitter-javascript = { version = "0.23" }
tree-sitter-kotlin-ng = { version = "1.1" }
tree-sitter-python = { version = "0.23" }
tree-sitter-rust = { version = "0.24" }
tree-sitter-scala = { version = "0.24" }
tree-sitter-typescript = { version = "0.23" }
uniffi = "0.29"
url = { version = "2.5" }
uuid = "1.8"
v_htmlescape = "0.15"
windows = { version = "0.61", features = [
"Win32_Devices_FunctionDiscovery",
"Win32_Foundation",
"Win32_Media_Audio",
"Win32_System_Com",
"Win32_System_Com_StructuredStorage",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_ProcessStatus",
"Win32_System_Threading",
"Win32_System_Variant",
"Win32_UI_Shell_PropertiesSystem",
] }
windows-core = { version = "0.61" }
y-octo = { path = "./packages/common/y-octo/core" }
y-sync = { version = "0.4" }
yrs = "0.23.0"
[profile.dev.package.sqlx-macros] [profile.dev.package.sqlx-macros]
opt-level = 3 opt-level = 3
@@ -141,6 +110,6 @@ lto = true
opt-level = 3 opt-level = 3
strip = "symbols" strip = "symbols"
# android uniffi bindgen requires symbols # android uniffi bindgen requires symbols
[profile.release.package.affine_mobile_native] [profile.release.package.affine_mobile_native]
strip = "none" strip = "none"

View File

@@ -2,7 +2,7 @@ Copyright (c) 2022-present TOEVERYTHING PTE. LTD. and its affiliates.
Portions of this software are licensed as follows: Portions of this software are licensed as follows:
- All content that resides under the "packages/backend" and "packages/common/native" directory of this repository, if that directory exists, is licensed under the license defined in "packages/backend/server/LICENSE". - All content that resides under the "packages/backend/server" directory of this repository, if that directory exists, is licensed under the license defined in "packages/backend/server/LICENSE".
- All third party components incorporated into the AFFiNE Software are licensed under the original license provided by the owner of the applicable component. - All third party components incorporated into the AFFiNE Software are licensed under the original license provided by the owner of the applicable component.
- Content outside of the above mentioned directories or restrictions above is available under the "MIT" license as defined in "LICENSE-MIT". - Content outside of the above mentioned directories or restrictions above is available under the "MIT" license as defined in "LICENSE-MIT".

View File

@@ -6,7 +6,7 @@
<br> <br>
</h1> </h1>
<a href="https://affine.pro/download"> <a href="https://affine.pro/download">
<img alt="affine logo" src="https://cdn.affine.pro/Github_hero_image2.png" style="width: 100%"> <img alt="affine logo" src="https://cdn.affine.pro/Github_hero_image1.png" style="width: 100%">
</a> </a>
<br/> <br/>
<p align="center"> <p align="center">
@@ -21,6 +21,23 @@
<br/> <br/>
<br/> <br/>
<div align="left" valign="middle">
<a href="https://runblaze.dev">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://www.runblaze.dev/logo_dark.png">
<img align="right" src="https://www.runblaze.dev/logo_light.png" height="102px"/>
</picture>
</a>
<br style="display: none;"/>
_Special thanks to [Blaze](https://runblaze.dev) for their support of this project. They provide high-performance Apple Silicon macOS and Linux (AMD64 & ARM64) runners for GitHub Actions, greatly reducing our automated build times._
</div>
<br/>
<br/>
<div align="center"> <div align="center">
<a href="https://affine.pro">Home Page</a> | <a href="https://affine.pro">Home Page</a> |
<a href="https://affine.pro/redirect/discord">Discord</a> | <a href="https://affine.pro/redirect/discord">Discord</a> |
@@ -64,7 +81,7 @@ Star us, and you will receive all release notifications from GitHub without any
**Multimodal AI partner ready to kick in any work** **Multimodal AI partner ready to kick in any work**
- Write up professional work report? Turn an outline into expressive and presentable slides? Summary an article into a well-structured mindmap? Sorting your job plan and backlog for tasks? Or... draw and code prototype apps and web pages directly all with one prompt? With you, [AFFiNE AI](https://affine.pro/ai) pushes your creativity to the edge of your imagination, just like [Canvas AI](https://affine.pro/blog/best-canvas-ai) to generate mind map for brainstorming. - Write up professional work report? Turn an outline into expressive and presentable slides? Summary an article into a well-structured mindmap? Sorting your job plan and backlog for tasks? Or... draw and code prototype apps and web pages directly all with one prompt? With you, [AFFiNE AI](https://affine.pro/ai) pushes your creativity to the edge of your imagination,just like [Canvas AI](https://affine.pro/blog/best-canvas-ai) to generate mind map for brainstorming.
**Local-first & Real-time collaborative** **Local-first & Real-time collaborative**
@@ -90,10 +107,10 @@ Thanks for checking us out, we appreciate your interest and sincerely hope that
## Contributing ## Contributing
| Bug Reports | Feature Requests | Questions/Discussions | AFFiNE Community | | Bug Reports | Feature Requests | Questions/Discussions | AFFiNE Community |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------- | | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------- |
| [Create a bug report](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=bug%2Cproduct-review&template=BUG-REPORT.yml&title=TITLE) | [Submit a feature request](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=feat%2Cproduct-review&template=FEATURE-REQUEST.yml&title=TITLE) | [Check GitHub Discussion](https://github.com/toeverything/AFFiNE/discussions) | [Visit the AFFiNE Community](https://community.affine.pro) | | [Create a bug report](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=bug%2Cproduct-review&template=BUG-REPORT.yml&title=TITLE) | [Submit a feature request](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=feat%2Cproduct-review&template=FEATURE-REQUEST.yml&title=TITLE) | [Check GitHub Discussion](https://github.com/toeverything/AFFiNE/discussions) | [Vist the AFFiNE Community](https://community.affine.pro) |
| Something isn't working as expected | An idea for a new feature, or improvements | Discuss and ask questions | A place to ask, learn and engage with others | | Something isn't working as expected | An idea for a new feature, or improvements | Discuss and ask questions | A place to ask, learn and engage with others |
Calling all developers, testers, tech writers and more! Contributions of all types are more than welcome, you can read more in [docs/types-of-contributions.md](docs/types-of-contributions.md). If you are interested in contributing code, read our [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) and feel free to check out our GitHub issues to get stuck in to show us what youre made of. Calling all developers, testers, tech writers and more! Contributions of all types are more than welcome, you can read more in [docs/types-of-contributions.md](docs/types-of-contributions.md). If you are interested in contributing code, read our [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) and feel free to check out our GitHub issues to get stuck in to show us what youre made of.
@@ -152,10 +169,8 @@ Welcome to the AFFiNE blog section! Here, youll find the latest insights, tip
We would also like to give thanks to open-source projects that make AFFiNE possible: We would also like to give thanks to open-source projects that make AFFiNE possible:
- [Blocksuite](https://github.com/toeverything/BlockSuite) - 💠 BlockSuite is the open-source collaborative editor project behind AFFiNE. - [Blocksuite](https://github.com/toeverything/BlockSuite) - 💠 BlockSuite is the open-source collaborative editor project behind AFFiNE.
- [y-octo](https://github.com/y-crdt/y-octo) - 🐙 y-octo is a native, high-performance, thread-safe YJS CRDT implementation, serving as the core engine enabling the AFFiNE Client/Server to achieve "local-first" functionality.
- [OctoBase](https://github.com/toeverything/OctoBase) - 🐙 OctoBase is the open-source database behind AFFiNE, local-first, yet collaborative. A light-weight, scalable, data engine written in Rust. - [OctoBase](https://github.com/toeverything/OctoBase) - 🐙 OctoBase is the open-source database behind AFFiNE, local-first, yet collaborative. A light-weight, scalable, data engine written in Rust.
- [yjs](https://github.com/yjs/yjs) - Fundamental support of CRDTs for our implementation on state management and data sync.
- [yjs](https://github.com/yjs/yjs) - Fundamental support of CRDTs for our implementation on state management and data sync on web.
- [electron](https://github.com/electron/electron) - Build cross-platform desktop apps with JavaScript, HTML, and CSS. - [electron](https://github.com/electron/electron) - Build cross-platform desktop apps with JavaScript, HTML, and CSS.
- [React](https://github.com/facebook/react) - The library for web and native user interfaces. - [React](https://github.com/facebook/react) - The library for web and native user interfaces.
- [napi-rs](https://github.com/napi-rs/napi-rs) - A framework for building compiled Node.js add-ons in Rust via Node-API. - [napi-rs](https://github.com/napi-rs/napi-rs) - A framework for building compiled Node.js add-ons in Rust via Node-API.
@@ -178,8 +193,6 @@ We would like to express our gratitude to all the individuals who have already c
Begin with Docker to deploy your own feature-rich, unrestricted version of AFFiNE. Our team is diligently updating to the latest version. For more information on how to self-host AFFiNE, please refer to our [documentation](https://docs.affine.pro/self-host-affine). Begin with Docker to deploy your own feature-rich, unrestricted version of AFFiNE. Our team is diligently updating to the latest version. For more information on how to self-host AFFiNE, please refer to our [documentation](https://docs.affine.pro/self-host-affine).
[![Run on Sealos](https://sealos.io/Deploy-on-Sealos.svg)](https://sealos.io/products/app-store/affine)
[![Run on ClawCloud](https://raw.githubusercontent.com/ClawCloud/Run-Template/refs/heads/main/Run-on-ClawCloud.svg)](https://template.run.claw.cloud/?openapp=system-fastdeploy%3FtemplateName%3Daffine) [![Run on ClawCloud](https://raw.githubusercontent.com/ClawCloud/Run-Template/refs/heads/main/Run-on-ClawCloud.svg)](https://template.run.claw.cloud/?openapp=system-fastdeploy%3FtemplateName%3Daffine)
## Hiring ## Hiring
@@ -206,6 +219,12 @@ See [BUILDING.md] for instructions on how to build AFFiNE from source code.
We welcome contributions from everyone. We welcome contributions from everyone.
See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details. See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details.
## Thanks
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" width="153" height="30" alt="Chromatic" /></a>
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
## License ## License
### Editions ### Editions

View File

@@ -6,14 +6,15 @@ We recommend users to always use the latest major version. Security updates will
| Version | Supported | | Version | Supported |
| --------------- | ------------------ | | --------------- | ------------------ |
| 0.26.x (stable) | :white_check_mark: | | 0.17.x (stable) | :white_check_mark: |
| < 0.26.x | :x: | | < 0.17.x | :x: |
## Reporting a Vulnerability ## Reporting a Vulnerability
We welcome you to provide us with bug reports via and email at [security@toeverything.info](mailto:security@toeverything.info) or submit directly on [GitHub](https://github.com/toeverything/AFFiNE/security), **we encourage you to submit the relevant information directly via GitHub**. We expect your report to contain at least the following for us to evaluate and reproduce: We welcome you to provide us with bug reports via and email at [security@toeverything.info](mailto:security@toeverything.info). We expect your report to contain at least the following for us to evaluate and reproduce:
1. Using platform and version, for example: 1. Using platform and version, for example:
- macos arm64 0.12.0-canary-202402220729-0868ac6 - macos arm64 0.12.0-canary-202402220729-0868ac6
- app.affine.pro 0.12.0-canary-202402220729-0868ac6 - app.affine.pro 0.12.0-canary-202402220729-0868ac6
@@ -21,6 +22,8 @@ We welcome you to provide us with bug reports via and email at [security@toevery
3. Your classification or analysis of the vulnerability (optional) 3. Your classification or analysis of the vulnerability (optional)
Since we are an open source project, we also welcome you to provide corresponding fix PRs, we will determine specific rewards based on the evaluation results. Since we are an open source project, we also welcome you to provide corresponding fix PRs.
We will provide bounties for vulnerabilities involving user information leakage, permission leakage, and unauthorized code execution. For other types of vulnerabilities, we will determine specific rewards based on the evaluation results.
If the vulnerability is caused by a library we depend on, we encourage you to submit a security report to the corresponding dependent library at the same time to benefit more users. If the vulnerability is caused by a library we depend on, we encourage you to submit a security report to the corresponding dependent library at the same time to benefit more users.

View File

@@ -48,7 +48,6 @@
"@blocksuite/affine-gfx-template": "workspace:*", "@blocksuite/affine-gfx-template": "workspace:*",
"@blocksuite/affine-gfx-text": "workspace:*", "@blocksuite/affine-gfx-text": "workspace:*",
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*", "@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
"@blocksuite/affine-inline-comment": "workspace:*",
"@blocksuite/affine-inline-footnote": "workspace:*", "@blocksuite/affine-inline-footnote": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*", "@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-inline-link": "workspace:*", "@blocksuite/affine-inline-link": "workspace:*",
@@ -79,7 +78,7 @@
"@blocksuite/std": "workspace:*", "@blocksuite/std": "workspace:*",
"@blocksuite/store": "workspace:*", "@blocksuite/store": "workspace:*",
"@blocksuite/sync": "workspace:*", "@blocksuite/sync": "workspace:*",
"rxjs": "^7.8.2" "rxjs": "^7.8.1"
}, },
"exports": { "exports": {
".": "./src/index.ts", ".": "./src/index.ts",
@@ -174,7 +173,6 @@
"./inlines/footnote": "./src/inlines/footnote/index.ts", "./inlines/footnote": "./src/inlines/footnote/index.ts",
"./inlines/footnote/view": "./src/inlines/footnote/view.ts", "./inlines/footnote/view": "./src/inlines/footnote/view.ts",
"./inlines/footnote/store": "./src/inlines/footnote/store.ts", "./inlines/footnote/store": "./src/inlines/footnote/store.ts",
"./inlines/comment": "./src/inlines/comment/index.ts",
"./inlines/latex": "./src/inlines/latex/index.ts", "./inlines/latex": "./src/inlines/latex/index.ts",
"./inlines/latex/store": "./src/inlines/latex/store.ts", "./inlines/latex/store": "./src/inlines/latex/store.ts",
"./inlines/latex/view": "./src/inlines/latex/view.ts", "./inlines/latex/view": "./src/inlines/latex/view.ts",
@@ -266,7 +264,6 @@
"./components/toggle-button": "./src/components/toggle-button.ts", "./components/toggle-button": "./src/components/toggle-button.ts",
"./components/toggle-switch": "./src/components/toggle-switch.ts", "./components/toggle-switch": "./src/components/toggle-switch.ts",
"./components/toolbar": "./src/components/toolbar.ts", "./components/toolbar": "./src/components/toolbar.ts",
"./components/tooltip": "./src/components/tooltip.ts",
"./components/view-dropdown-menu": "./src/components/view-dropdown-menu.ts", "./components/view-dropdown-menu": "./src/components/view-dropdown-menu.ts",
"./components/tooltip-content-with-shortcut": "./src/components/tooltip-content-with-shortcut.ts", "./components/tooltip-content-with-shortcut": "./src/components/tooltip-content-with-shortcut.ts",
"./components/resource": "./src/components/resource.ts", "./components/resource": "./src/components/resource.ts",
@@ -286,7 +283,6 @@
"./sync": "./src/sync/index.ts", "./sync": "./src/sync/index.ts",
"./extensions/store": "./src/extensions/store.ts", "./extensions/store": "./src/extensions/store.ts",
"./extensions/view": "./src/extensions/view.ts", "./extensions/view": "./src/extensions/view.ts",
"./foundation/clipboard": "./src/foundation/clipboard.ts",
"./foundation/store": "./src/foundation/store.ts", "./foundation/store": "./src/foundation/store.ts",
"./foundation/view": "./src/foundation/view.ts" "./foundation/view": "./src/foundation/view.ts"
}, },
@@ -296,10 +292,10 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1", "version": "0.21.0",
"devDependencies": { "devDependencies": {
"@vanilla-extract/vite-plugin": "^5.0.0", "@vanilla-extract/vite-plugin": "^5.0.0",
"msw": "^2.12.4", "msw": "^2.8.4",
"vitest": "^3.2.4" "vitest": "3.1.3"
} }
} }

Some files were not shown because too many files have changed in this diff Show More