mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 08:38:34 +00:00
Compare commits
224 Commits
v0.26.0-be
...
v0.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7930671a51 | ||
|
|
3acd546337 | ||
|
|
3bcf30348a | ||
|
|
74909d4fd5 | ||
|
|
691eb3cb2d | ||
|
|
6ddc56632e | ||
|
|
5c183c8353 | ||
|
|
5e62fec49c | ||
|
|
8f590ff360 | ||
|
|
24bb109ec3 | ||
|
|
efdf6c2b81 | ||
|
|
b40051e4a4 | ||
|
|
02506bcda4 | ||
|
|
58dc0f2b2f | ||
|
|
5a648baff4 | ||
|
|
5ec3279910 | ||
|
|
e29db64e7f | ||
|
|
f8ec1ff6d7 | ||
|
|
e1a62c1f03 | ||
|
|
27fa5654af | ||
|
|
0a7852b1eb | ||
|
|
0ca9f2cc6d | ||
|
|
4750e2535f | ||
|
|
9ff483cd4e | ||
|
|
c38dd1cc5e | ||
|
|
735d91a003 | ||
|
|
02946a0f4f | ||
|
|
22aa7ff25f | ||
|
|
912cd7fb8b | ||
|
|
559d9bb9a8 | ||
|
|
944f4ee895 | ||
|
|
94d518e09a | ||
|
|
b9dff62f1f | ||
|
|
1bc8d6a0b8 | ||
|
|
04ade02254 | ||
|
|
655b6d5399 | ||
|
|
9df1eac0b8 | ||
|
|
e58b21c599 | ||
|
|
c1e53b90a2 | ||
|
|
1fecb22627 | ||
|
|
770bde8088 | ||
|
|
bb7ed6869e | ||
|
|
ac55ae467c | ||
|
|
626e9f96e9 | ||
|
|
0efcaa1b6b | ||
|
|
8a1f022c49 | ||
|
|
7faa909ab1 | ||
|
|
c9a7744270 | ||
|
|
2c9c4c2cd3 | ||
|
|
b0bb925ef6 | ||
|
|
272835cfba | ||
|
|
0c66780fda | ||
|
|
74e6fd8d4a | ||
|
|
6815110727 | ||
|
|
7bda0f87e6 | ||
|
|
38521dc8b2 | ||
|
|
1effd9b22e | ||
|
|
d7a7c6aa50 | ||
|
|
4de1baf2eb | ||
|
|
d66f7da3c0 | ||
|
|
9b948613dc | ||
|
|
77200ed8fc | ||
|
|
d42c53c3da | ||
|
|
28316861e3 | ||
|
|
9bc42c7eca | ||
|
|
994a5d9b54 | ||
|
|
0ddbe89d88 | ||
|
|
fdffac6911 | ||
|
|
72466d0768 | ||
|
|
4a982b3bcf | ||
|
|
348c56ba30 | ||
|
|
83d92af07d | ||
|
|
b4397eb64f | ||
|
|
2ae7bea5b1 | ||
|
|
1908d1a213 | ||
|
|
e6530f5017 | ||
|
|
9ef492f5f8 | ||
|
|
a41d9c0ac2 | ||
|
|
d4dd352ebe | ||
|
|
c23d31f912 | ||
|
|
94b5563a0e | ||
|
|
446d2d3370 | ||
|
|
c8492d2014 | ||
|
|
1a223a99d1 | ||
|
|
b8a2438679 | ||
|
|
f38978e7c9 | ||
|
|
44dbe39001 | ||
|
|
424580971e | ||
|
|
6d552ce85e | ||
|
|
031dd6d4ea | ||
|
|
d2503d46eb | ||
|
|
1d701b9a8e | ||
|
|
29c3744c53 | ||
|
|
794f19637d | ||
|
|
822c01c360 | ||
|
|
76da19bac6 | ||
|
|
c53638c08d | ||
|
|
aa31043871 | ||
|
|
e2d6d5548f | ||
|
|
8f1bfa46a9 | ||
|
|
0a607e0450 | ||
|
|
e41abe4cfe | ||
|
|
5a846755a8 | ||
|
|
e99891f22d | ||
|
|
eaf7965e79 | ||
|
|
1d2b4c6057 | ||
|
|
e609ab201f | ||
|
|
43e996355a | ||
|
|
d41c7fb899 | ||
|
|
093851ec0c | ||
|
|
c5f5e606b0 | ||
|
|
b45c8b582e | ||
|
|
d94ad8caac | ||
|
|
8dfc95d002 | ||
|
|
466b1bb173 | ||
|
|
1a93429ffa | ||
|
|
e5f150f093 | ||
|
|
e6d709a8a0 | ||
|
|
708e361264 | ||
|
|
34ad5cdaef | ||
|
|
65c6479ee4 | ||
|
|
394d535906 | ||
|
|
70cf3176b7 | ||
|
|
c11fe97b47 | ||
|
|
d5926fce8c | ||
|
|
9ba6539b5b | ||
|
|
8ae091b8c3 | ||
|
|
d04585f630 | ||
|
|
dedb4ba833 | ||
|
|
1887a36ca5 | ||
|
|
4787fef74a | ||
|
|
5be4873d36 | ||
|
|
0e342999fd | ||
|
|
f16f02b6b0 | ||
|
|
3a3053fb49 | ||
|
|
425439ec42 | ||
|
|
2a4a2db6f6 | ||
|
|
562013e3bd | ||
|
|
1f7c1ef91e | ||
|
|
1a80c95ae3 | ||
|
|
bbb97d06da | ||
|
|
20ce8c0cd5 | ||
|
|
940bf15ac4 | ||
|
|
5fe8fae6ae | ||
|
|
826281453f | ||
|
|
979ef24c39 | ||
|
|
fad07b2cab | ||
|
|
e147487d9c | ||
|
|
1e15ae5766 | ||
|
|
5d5110126e | ||
|
|
b467ab1d76 | ||
|
|
635ec0a401 | ||
|
|
578f92e376 | ||
|
|
00604bd4b0 | ||
|
|
8f09df655c | ||
|
|
fbc196e828 | ||
|
|
d120f3ada8 | ||
|
|
d24009f494 | ||
|
|
721e5e4dd9 | ||
|
|
612fbb4f97 | ||
|
|
bba27fb02e | ||
|
|
92e5b3d937 | ||
|
|
014566fcc9 | ||
|
|
3fbeefe329 | ||
|
|
ce09a7c09f | ||
|
|
4757a95528 | ||
|
|
5e4cd0f81d | ||
|
|
536bf01dd9 | ||
|
|
99a60c37d0 | ||
|
|
bfe6f0b4dd | ||
|
|
aac0d24aed | ||
|
|
c60648ce9b | ||
|
|
48e33e2d73 | ||
|
|
3236a91d0b | ||
|
|
d790ead446 | ||
|
|
12ed9db207 | ||
|
|
5a4bb70869 | ||
|
|
de52651c7f | ||
|
|
ce45c15d13 | ||
|
|
e65cae121e | ||
|
|
51b6f5a105 | ||
|
|
bfbb7e9e6b | ||
|
|
03dea53b30 | ||
|
|
0bdecc776d | ||
|
|
b4389b7380 | ||
|
|
d796fc2ab0 | ||
|
|
a8dc00b406 | ||
|
|
76039d8d3e | ||
|
|
b6b5b58889 | ||
|
|
6f5f0ab986 | ||
|
|
f7b07f4216 | ||
|
|
df7f782e05 | ||
|
|
3fb30a6cce | ||
|
|
2c5fa4f008 | ||
|
|
4433a72b96 | ||
|
|
1853dd9895 | ||
|
|
d98586773c | ||
|
|
bf30185c65 | ||
|
|
09e38e4a91 | ||
|
|
97f41ab620 | ||
|
|
0cbeff6f61 | ||
|
|
f3ed31b4b7 | ||
|
|
020f86bcc1 | ||
|
|
997cf3bd92 | ||
|
|
af399adf14 | ||
|
|
255e53673e | ||
|
|
3ba1b3fc61 | ||
|
|
135eb07f9e | ||
|
|
3362069ae8 | ||
|
|
fe7400abe1 | ||
|
|
4cca8a16ab | ||
|
|
5a3e5a1565 | ||
|
|
87063d20ce | ||
|
|
7b6728e8a9 | ||
|
|
1bff46d5f0 | ||
|
|
81bac496b4 | ||
|
|
b31aa44d40 | ||
|
|
bff8ca6178 | ||
|
|
be33cfd174 | ||
|
|
0eb11d14ad | ||
|
|
b0d8c8e14f | ||
|
|
72378abe56 | ||
|
|
fc36aac0fb | ||
|
|
e7f4d82881 |
20
.codesandbox/task.json
Normal file
20
.codesandbox/task.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://codesandbox.io/schemas/tasks.json",
|
||||
"setupTasks": [
|
||||
{
|
||||
"name": "Install Dependencies",
|
||||
"command": "yarn install"
|
||||
}
|
||||
],
|
||||
|
||||
"tasks": {
|
||||
"start-web": {
|
||||
"name": "Start Web",
|
||||
"command": "yarn nx dev @affine/web --port 8080",
|
||||
"runAtStart": true,
|
||||
"preview": {
|
||||
"port": 8080
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
"server",
|
||||
"web",
|
||||
"docs",
|
||||
"storybook",
|
||||
"component",
|
||||
"workspace",
|
||||
"env",
|
||||
@@ -21,7 +22,9 @@
|
||||
"templates",
|
||||
"y-indexeddb",
|
||||
"debug",
|
||||
"theme"
|
||||
"storage",
|
||||
"infra",
|
||||
"plugin-infra"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,3 +8,4 @@ _next
|
||||
lib
|
||||
.eslintrc.js
|
||||
packages/i18n/src/i18n-generated.ts
|
||||
e2e-dist-*
|
||||
|
||||
16
.eslintrc.js
16
.eslintrc.js
@@ -21,6 +21,11 @@ const createPattern = packageName => [
|
||||
message: 'Do not import package itself',
|
||||
allowTypeImports: false,
|
||||
},
|
||||
{
|
||||
group: ['@blocksuite/store'],
|
||||
message: "Import from '@blocksuite/global/utils'",
|
||||
importNames: ['assertExists', 'assertEquals'],
|
||||
},
|
||||
];
|
||||
|
||||
const allPackages = [
|
||||
@@ -41,6 +46,7 @@ const allPackages = [
|
||||
'apps/web',
|
||||
'apps/server',
|
||||
'apps/electron',
|
||||
'apps/storybook',
|
||||
'plugins/copilot',
|
||||
'plugins/bookmark-block',
|
||||
];
|
||||
@@ -64,6 +70,7 @@ const config = {
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
@@ -81,7 +88,7 @@ const config = {
|
||||
'@typescript-eslint',
|
||||
'simple-import-sort',
|
||||
'sonarjs',
|
||||
'import',
|
||||
'i',
|
||||
'unused-imports',
|
||||
'unicorn',
|
||||
],
|
||||
@@ -132,6 +139,11 @@ const config = {
|
||||
message: "Don't import from src",
|
||||
allowTypeImports: false,
|
||||
},
|
||||
{
|
||||
group: ['@blocksuite/store'],
|
||||
message: "Import from '@blocksuite/global/utils'",
|
||||
importNames: ['assertExists', 'assertEquals'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -157,6 +169,7 @@ const config = {
|
||||
'sonarjs/no-duplicated-branches': 'error',
|
||||
'sonarjs/no-collection-size-mischeck': 'error',
|
||||
'sonarjs/no-useless-catch': 'error',
|
||||
'sonarjs/no-identical-functions': 'error',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
@@ -201,6 +214,7 @@ const config = {
|
||||
'scripts/**/*',
|
||||
'**/benchmark/**/*',
|
||||
'**/__debug__/**/*',
|
||||
'**/e2e/**/*',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-non-null-assertion': 0,
|
||||
|
||||
16
.github/actions/build-rust/action.yml
vendored
16
.github/actions/build-rust/action.yml
vendored
@@ -27,11 +27,11 @@ runs:
|
||||
.cargo-cache
|
||||
target/${{ inputs.target }}
|
||||
key: stable-${{ inputs.target }}-cargo-cache
|
||||
|
||||
- name: Build
|
||||
if: ${{ inputs.target != 'x86_64-unknown-linux-gnu' && inputs.target != 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
run: yarn nx build @affine/native --target ${{ inputs.target }}
|
||||
run: |
|
||||
yarn nx build @affine/native --target ${{ inputs.target }}
|
||||
env:
|
||||
NX_CLOUD_ACCESS_TOKEN: ${{ inputs.nx_token }}
|
||||
|
||||
@@ -41,11 +41,12 @@ runs:
|
||||
with:
|
||||
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
|
||||
options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build -e NX_CLOUD_ACCESS_TOKEN=${{ inputs.nx_token }}
|
||||
run: >-
|
||||
export CC=x86_64-unknown-linux-gnu-gcc &&
|
||||
export CC_x86_64_unknown_linux_gnu=x86_64-unknown-linux-gnu-gcc &&
|
||||
yarn nx build @affine/native --target ${{ inputs.target }} &&
|
||||
run: |
|
||||
export CC=x86_64-unknown-linux-gnu-gcc
|
||||
export CC_x86_64_unknown_linux_gnu=x86_64-unknown-linux-gnu-gcc
|
||||
yarn nx build @affine/native --target ${{ inputs.target }}
|
||||
chmod -R 777 node_modules/.cache
|
||||
chmod -R 777 target
|
||||
|
||||
- name: Build
|
||||
if: ${{ inputs.target == 'aarch64-unknown-linux-gnu' }}
|
||||
@@ -53,6 +54,7 @@ runs:
|
||||
with:
|
||||
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
|
||||
options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build -e NX_CLOUD_ACCESS_TOKEN=${{ inputs.nx_token }}
|
||||
run: >-
|
||||
run: |
|
||||
yarn nx build @affine/native --target ${{ inputs.target }}
|
||||
chmod -R 777 node_modules/.cache
|
||||
chmod -R 777 target
|
||||
|
||||
72
.github/actions/setup-node/action.yml
vendored
72
.github/actions/setup-node/action.yml
vendored
@@ -13,10 +13,18 @@ inputs:
|
||||
description: 'Run the install step for Playwright.'
|
||||
required: false
|
||||
default: 'false'
|
||||
electron-install:
|
||||
description: 'Download the Electron binary'
|
||||
required: false
|
||||
default: 'true'
|
||||
npm-token:
|
||||
description: 'The NPM token to use for private packages.'
|
||||
required: false
|
||||
default: ''
|
||||
hard-link-nm:
|
||||
description: 'set nmMode to hardlinks-local in .yarnrc.yml'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
@@ -29,33 +37,10 @@ runs:
|
||||
scope: '@toeverything'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Expose yarn config as "$GITHUB_OUTPUT"
|
||||
id: yarn-config
|
||||
- name: Set nmMode
|
||||
if: ${{ inputs.hard-link-nm == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Restore yarn cache
|
||||
uses: actions/cache@v3
|
||||
id: yarn-download-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
|
||||
key: yarn-download-cache-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
yarn-download-cache-
|
||||
|
||||
- name: Restore node_modules cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Restore yarn install state
|
||||
id: yarn-install-state-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .yarn/ci-cache/
|
||||
key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
|
||||
run: yarn config set nmMode hardlinks-local
|
||||
|
||||
- name: yarn install
|
||||
if: ${{ inputs.package-install == 'true' }}
|
||||
@@ -64,9 +49,9 @@ runs:
|
||||
run: yarn install ${{ inputs.extra-flags }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
|
||||
YARN_ENABLE_GLOBAL_CACHE: 'false'
|
||||
YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
|
||||
- name: yarn install (try again)
|
||||
if: ${{ steps.install.outcome == 'failure' }}
|
||||
@@ -74,9 +59,9 @@ runs:
|
||||
run: yarn install ${{ inputs.extra-flags }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
|
||||
YARN_ENABLE_GLOBAL_CACHE: 'false'
|
||||
YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
|
||||
- name: Get installed Playwright version
|
||||
id: playwright-version
|
||||
@@ -113,3 +98,30 @@ runs:
|
||||
shell: bash
|
||||
if: inputs.playwright-install == 'true' && steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: yarn playwright install --with-deps
|
||||
|
||||
- name: Get installed Electron version
|
||||
id: electron-version
|
||||
if: ${{ inputs.electron-install == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "version=$(yarn why --json electron | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://' | head -n 1)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: electron-cache
|
||||
if: ${{ inputs.electron-install == 'true' }}
|
||||
with:
|
||||
path: 'node_modules/.cache/electron'
|
||||
key: '${{ runner.os }}-electron-${{ steps.electron-version.outputs.version }}'
|
||||
restore-keys: |
|
||||
${{ runner.os }}-electron-
|
||||
|
||||
- name: Install Electron binary
|
||||
shell: bash
|
||||
if: inputs.electron-install == 'true'
|
||||
run: node apps/electron/node_modules/electron/install.js
|
||||
env:
|
||||
ELECTRON_OVERRIDE_DIST_PATH: ./node_modules/.cache/electron
|
||||
|
||||
- name: Build Infra
|
||||
shell: bash
|
||||
run: yarn run build:infra
|
||||
|
||||
31
.github/actions/setup-rust/action.yml
vendored
Normal file
31
.github/actions/setup-rust/action.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: 'AFFiNE Rust setup'
|
||||
description: 'Rust setup, including cache configuration'
|
||||
inputs:
|
||||
target:
|
||||
description: 'Cargo target'
|
||||
required: true
|
||||
toolchain:
|
||||
description: 'Rustup toolchain'
|
||||
required: false
|
||||
default: 'stable'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ inputs.toolchain }}
|
||||
targets: ${{ inputs.target }}
|
||||
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: cargo-cache-${{ runner.os }}-${{ inputs.toolchain }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
cargo-cache-${{ runner.os }}-${{ inputs.toolchain }}-
|
||||
2
.github/deployment/front/affine.nginx.conf
vendored
2
.github/deployment/front/affine.nginx.conf
vendored
@@ -3,7 +3,7 @@ server {
|
||||
root /app/dist;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/index.html $uri.html =404;;
|
||||
try_files $uri $uri/index.html $uri.html =404;
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
1
.github/helm/affine-cloud/.gitignore
vendored
Normal file
1
.github/helm/affine-cloud/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
charts/
|
||||
2
.github/helm/affine-cloud/.helmignore
vendored
2
.github/helm/affine-cloud/.helmignore
vendored
@@ -20,4 +20,4 @@
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
.vscode/
|
||||
4
.github/helm/affine-cloud/Chart.yaml
vendored
4
.github/helm/affine-cloud/Chart.yaml
vendored
@@ -3,8 +3,8 @@ name: affine-cloud
|
||||
description: A Helm chart for AFFiNE Cloud
|
||||
|
||||
type: application
|
||||
version: 0.6.0
|
||||
appVersion: '0.6.0'
|
||||
version: 0.6.1
|
||||
appVersion: '0.6.1'
|
||||
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
|
||||
Binary file not shown.
30
.github/helm/affine-cloud/readme.md
vendored
Normal file
30
.github/helm/affine-cloud/readme.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Helm Chart Configuration
|
||||
|
||||
The following table lists the configurable parameters of this Helm chart and their default values.
|
||||
|
||||
## AFFiNE Cloud Server parameters
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| ------------------------------ | -------------------------------------------------- | ------------------ |
|
||||
| `affineCloud.tag` | The Docker tag of the AffineCloud image to be used | `'nightly-latest'` |
|
||||
| `affineCloud.resources.cpu` | The CPU resources allocated for AffineCloud | `'250m'` |
|
||||
| `affineCloud.resources.memory` | The memory resources allocated for AffineCloud | `'0.5Gi'` |
|
||||
| `affineCloud.signKey` | The key used to sign the JWT tokens | `'c2VjcmV0'` |
|
||||
| `affineCloud.service.type` | The type of the Kubernetes service | `'ClusterIP'` |
|
||||
| `affineCloud.service.port` | The port of the Kubernetes service | `'http'` |
|
||||
| `affineCloud.mail.account` | The email account used to send emails | `''` |
|
||||
| `affineCloud.mail.password` | The password of the email account | `''` |
|
||||
|
||||
## PostgreSQL parameters
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| -------------------------------------------- | ------------------------------------------------------------------------------------- | ------------ |
|
||||
| `postgresql.auth.username` | Username for the PostgreSQL database | `'affine'` |
|
||||
| `postgresql.auth.password` | Password for the PostgreSQL database. Please change this for production environments. | `'password'` |
|
||||
| `postgresql.auth.database` | The name of the default database that will be created on image startup | `'affine'` |
|
||||
| `postgresql.primary.resources.limits.cpu` | The CPU resources allocated for the PostgreSQL primary node | `'500m'` |
|
||||
| `postgresql.primary.resources.limits.memory` | The memory resources allocated for the PostgreSQL primary node | `'0.5Gi'` |
|
||||
|
||||
For more postgres parameters, please refer to: https://artifacthub.io/packages/helm/bitnami/postgresql
|
||||
|
||||
Please note that for the `postgresql.auth.password`, you should provide your own password for production environments. The default value is provided only for demonstration purposes.
|
||||
51
.github/helm/affine-cloud/templates/_helper.tpl
vendored
Normal file
51
.github/helm/affine-cloud/templates/_helper.tpl
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "affine-cloud.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 "affine-cloud.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 "affine-cloud.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "affine-cloud.labels" -}}
|
||||
helm.sh/chart: {{ include "affine-cloud.chart" . }}
|
||||
{{ include "affine-cloud.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "affine-cloud.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "affine-cloud.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
@@ -1,12 +1,14 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: affine-cloud
|
||||
name: "{{ include "affine-cloud.fullname" . }}"
|
||||
labels:
|
||||
{{- include "affine-cloud.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: affine-cloud
|
||||
{{- include "affine-cloud.selectorLabels" . | nindent 6 }}
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
@@ -14,7 +16,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: affine-cloud
|
||||
{{- include "affine-cloud.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
restartPolicy: Always
|
||||
containers:
|
||||
@@ -28,9 +30,9 @@ spec:
|
||||
- name: PG_DATABASE
|
||||
value: "{{ .Values.postgresql.auth.database }}"
|
||||
- name: PG_HOST
|
||||
value: "{{ .Release.Name }}-postgresql"
|
||||
value: "{{ .Values.postgresql.fullnameOverride | default (printf "%s-postgresql" .Release.Name) }}"
|
||||
- name: DATABASE_URL
|
||||
value: "{{ default "postgresql://$(PG_USER):$(PG_PASS)@$(PG_HOST)/$(PG_DATABASE)" .Values.affineCloud.databaseUrl }}"
|
||||
value: "{{ .Values.affineCloud.databaseUrl | default "postgresql://$(PG_USER):$(PG_PASS)@$(PG_HOST)/$(PG_DATABASE)" }}"
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: affine-cloud-secret
|
||||
|
||||
@@ -4,7 +4,6 @@ metadata:
|
||||
name: affine-cloud-secret
|
||||
type: Opaque
|
||||
data:
|
||||
# only for demo, please modify it at prod env
|
||||
SIGN_KEY: TUFtdFdzQTJhdGJuem01TA==
|
||||
# MAIL_ACCOUNT: XXXX
|
||||
# MAIL_PASSWORD: XXXX
|
||||
SIGN_KEY: "{{ .Values.affineCloud.signKey }}"
|
||||
MAIL_ACCOUNT: "{{ .Values.affineCloud.mail.account }}"
|
||||
MAIL_PASSWORD: "{{ .Values.affineCloud.mail.password }}"
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: affine-cloud
|
||||
name: "{{ include "affine-cloud.fullname" . }}"
|
||||
labels:
|
||||
{{- include "affine-cloud.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: affine-cloud
|
||||
type: "{{ .Values.affineCloud.service.type }}"
|
||||
ports:
|
||||
- name: affine-cloud
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 3000
|
||||
port: {{ .Values.affineCloud.service.port }}
|
||||
targetPort: 3000
|
||||
selector:
|
||||
{{- include "affine-cloud.selectorLabels" . | nindent 4 }}
|
||||
|
||||
13
.github/helm/affine-cloud/values.yaml
vendored
13
.github/helm/affine-cloud/values.yaml
vendored
@@ -1,13 +1,22 @@
|
||||
affineCloud:
|
||||
tag: 'nightly-latest'
|
||||
tag: 'canary-5e0d5e0cc65ea46f326fdde12658bfac59b38c9f-0949'
|
||||
# databaseUrl: 'postgresql://affine:password@affine-cloud-postgresql:5432/affine'
|
||||
signKey: TUFtdFdzQTJhdGJuem01TA==
|
||||
mail:
|
||||
account: ''
|
||||
password: ''
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
resources:
|
||||
cpu: '250m'
|
||||
memory: 0.5Gi
|
||||
postgresql:
|
||||
fullnameOverride: tcp-postgresql
|
||||
auth:
|
||||
# only for demo, please modify it at prod env
|
||||
username: affine
|
||||
password: XJYMLnuBJS27a2du
|
||||
password: password
|
||||
database: affine
|
||||
primary:
|
||||
initdb:
|
||||
|
||||
23
.github/helm/affine/.helmignore
vendored
Normal file
23
.github/helm/affine/.helmignore
vendored
Normal 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/
|
||||
6
.github/helm/affine/Chart.yaml
vendored
Normal file
6
.github/helm/affine/Chart.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: affine
|
||||
description: AFFiNE cloud chart
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: '0.7.0-canary.18'
|
||||
6
.github/helm/affine/charts/graphql/Chart.yaml
vendored
Normal file
6
.github/helm/affine/charts/graphql/Chart.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: graphql
|
||||
description: AFFiNE GraphQL server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: '0.7.0-canary.18'
|
||||
16
.github/helm/affine/charts/graphql/templates/NOTES.txt
vendored
Normal file
16
.github/helm/affine/charts/graphql/templates/NOTES.txt
vendored
Normal 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 "graphql.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 "graphql.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "graphql.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 "graphql.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 }}
|
||||
132
.github/helm/affine/charts/graphql/templates/_helpers.tpl
vendored
Normal file
132
.github/helm/affine/charts/graphql/templates/_helpers.tpl
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "graphql.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 "graphql.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 "graphql.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "graphql.labels" -}}
|
||||
helm.sh/chart: {{ include "graphql.chart" . }}
|
||||
{{ include "graphql.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "graphql.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "graphql.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "graphql.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "graphql.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "jwt.key" -}}
|
||||
{{- $secret := lookup "v1" "Secret" .Release.Namespace .Values.app.jwt.secretName -}}
|
||||
{{- if and $secret $secret.data.private -}}
|
||||
{{/*
|
||||
Reusing existing secret data
|
||||
*/}}
|
||||
key: {{ $secret.data.private }}
|
||||
{{- else -}}
|
||||
{{/*
|
||||
Generate new data
|
||||
*/}}
|
||||
key: {{ genPrivateKey "ecdsa" | b64enc }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "objectStorage.r2" -}}
|
||||
{{- $secret := lookup "v1" "Secret" .Release.Namespace .Values.app.objectStorage.r2.secretName -}}
|
||||
{{- if $secret -}}
|
||||
{{/*
|
||||
Reusing existing secret data
|
||||
*/}}
|
||||
accountId: {{ $secret.data.accountId }}
|
||||
accessKeyId: {{ $secret.data.accessKeyId }}
|
||||
secretAccessKey: {{ $secret.data.secretAccessKey }}
|
||||
bucket: {{ $secret.data.bucket }}
|
||||
{{- else -}}
|
||||
{{/*
|
||||
Generate new data
|
||||
*/}}
|
||||
accountId: {{ .Values.app.objectStorage.r2.accountId | b64enc }}
|
||||
accessKeyId: {{ .Values.app.objectStorage.r2.accessKeyId | b64enc }}
|
||||
secretAccessKey: {{ .Values.app.objectStorage.r2.secretAccessKey | b64enc }}
|
||||
bucket: {{ .Values.app.objectStorage.r2.bucket | b64enc }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "objectStorage.oauth.google" -}}
|
||||
{{- $secret := lookup "v1" "Secret" .Release.Namespace .Values.app.oauth.google.secretName -}}
|
||||
{{- if $secret -}}
|
||||
{{/*
|
||||
Reusing existing secret data
|
||||
*/}}
|
||||
clientId: {{ $secret.data.clientId }}
|
||||
clientSecret: {{ $secret.data.clientSecret }}
|
||||
{{- else -}}
|
||||
{{/*
|
||||
Generate new data
|
||||
*/}}
|
||||
clientId: "{{ .Values.app.oauth.google.clientId | b64enc }}"
|
||||
clientSecret: "{{ .Values.app.oauth.google.clientSecret | b64enc }}"
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "objectStorage.oauth.github" -}}
|
||||
{{- $secret := lookup "v1" "Secret" .Release.Namespace .Values.app.oauth.github.secretName -}}
|
||||
{{- if $secret -}}
|
||||
{{/*
|
||||
Reusing existing secret data
|
||||
*/}}
|
||||
clientId: {{ $secret.data.clientId }}
|
||||
clientSecret: {{ $secret.data.clientSecret }}
|
||||
{{- else -}}
|
||||
{{/*
|
||||
Generate new data
|
||||
*/}}
|
||||
clientId: "{{ .Values.app.oauth.github.clientId | b64enc }}"
|
||||
clientSecret: "{{ .Values.app.oauth.github.clientSecret | b64enc }}"
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
126
.github/helm/affine/charts/graphql/templates/deployment.yaml
vendored
Normal file
126
.github/helm/affine/charts/graphql/templates/deployment.yaml
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "graphql.fullname" . }}
|
||||
labels:
|
||||
{{- include "graphql.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "graphql.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "graphql.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "graphql.serviceAccountName" . }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
env:
|
||||
- name: AUTH_PRIVATE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.jwt.secretName }}"
|
||||
key: key
|
||||
- name: NODE_ENV
|
||||
value: "{{ .Values.env }}"
|
||||
- name: DATABSE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-postgresql
|
||||
key: postgres-password
|
||||
- name: DATABASE_URL
|
||||
value: postgres://{{ .Values.database.user }}:$(DATABSE_PASSWORD)@{{ .Values.database.url }}:{{ .Values.database.port }}/{{ .Values.database.name }}
|
||||
- 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: ENABLE_R2_OBJECT_STORAGE
|
||||
value: "{{ .Values.app.objectStorage.r2.enabled }}"
|
||||
{{ if .Values.app.objectStorage.r2.enabled }}
|
||||
- name: R2_OBJECT_STORAGE_ACCOUNT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.objectStorage.r2.secretName }}"
|
||||
key: accountId
|
||||
- name: R2_OBJECT_STORAGE_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.objectStorage.r2.secretName }}"
|
||||
key: accessKeyId
|
||||
- name: R2_OBJECT_STORAGE_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.objectStorage.r2.secretName }}"
|
||||
key: secretAccessKey
|
||||
- name: R2_OBJECT_STORAGE_BUCKET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.objectStorage.r2.secretName }}"
|
||||
key: bucket
|
||||
{{ end }}
|
||||
{{ if .Values.app.oauth.google.enabled }}
|
||||
- name: OAUTH_GOOGLE_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.oauth.google.secretName }}"
|
||||
key: clientId
|
||||
- name: OAUTH_GOOGLE_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.oauth.google.secretName }}"
|
||||
key: clientSecret
|
||||
{{ end }}
|
||||
{{ if .Values.app.oauth.github.enabled }}
|
||||
- name: OAUTH_GITHUB_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.oauth.github.secretName }}"
|
||||
key: clientId
|
||||
- name: OAUTH_GITHUB_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.oauth.github.secretName }}"
|
||||
key: clientSecret
|
||||
{{ end }}
|
||||
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 }}
|
||||
7
.github/helm/affine/charts/graphql/templates/jwt-secret.yaml
vendored
Normal file
7
.github/helm/affine/charts/graphql/templates/jwt-secret.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ .Values.app.jwt.secretName }}"
|
||||
type: Opaque
|
||||
data:
|
||||
{{- ( include "jwt.key" . ) | indent 2 -}}
|
||||
34
.github/helm/affine/charts/graphql/templates/migration.yaml
vendored
Normal file
34
.github/helm/affine/charts/graphql/templates/migration.yaml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ include "graphql.fullname" . }}-database-migration
|
||||
labels:
|
||||
{{- include "graphql.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install,pre-upgrade
|
||||
"helm.sh/hook-weight": "-1"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation
|
||||
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
command: ["yarn", "prisma", "migrate", "deploy"]
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: "{{ .Values.env }}"
|
||||
- name: DATABSE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-postgresql
|
||||
key: postgres-password
|
||||
- name: DATABASE_URL
|
||||
value: postgres://{{ .Values.database.user }}:$(DATABSE_PASSWORD)@{{ .Values.database.url }}:{{ .Values.database.port }}/{{ .Values.database.name }}
|
||||
resources:
|
||||
requests:
|
||||
cpu: '100m'
|
||||
memory: '200Mi'
|
||||
restartPolicy: Never
|
||||
backoffLimit: 1
|
||||
10
.github/helm/affine/charts/graphql/templates/oauth-github-secret.yaml
vendored
Normal file
10
.github/helm/affine/charts/graphql/templates/oauth-github-secret.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{{- if .Values.app.oauth.github.enabled -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ .Values.app.oauth.github.secretName }}"
|
||||
type: Opaque
|
||||
data:
|
||||
{{- ( include "objectStorage.oauth.github" . ) | indent 2 -}}
|
||||
|
||||
{{- end }}
|
||||
10
.github/helm/affine/charts/graphql/templates/oauth-google-secret.yaml
vendored
Normal file
10
.github/helm/affine/charts/graphql/templates/oauth-google-secret.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{{- if .Values.app.oauth.google.enabled -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ .Values.app.oauth.google.secretName }}"
|
||||
type: Opaque
|
||||
data:
|
||||
{{- ( include "objectStorage.oauth.google" . ) | indent 2 -}}
|
||||
|
||||
{{- end }}
|
||||
9
.github/helm/affine/charts/graphql/templates/r2-secret.yaml
vendored
Normal file
9
.github/helm/affine/charts/graphql/templates/r2-secret.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{{- if .Values.app.objectStorage.r2.enabled -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ .Values.app.objectStorage.r2.secretName }}"
|
||||
type: Opaque
|
||||
data:
|
||||
{{- ( include "objectStorage.r2" . ) | indent 2 -}}
|
||||
{{- end }}
|
||||
15
.github/helm/affine/charts/graphql/templates/service.yaml
vendored
Normal file
15
.github/helm/affine/charts/graphql/templates/service.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "graphql.fullname" . }}
|
||||
labels:
|
||||
{{- include "graphql.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "graphql.selectorLabels" . | nindent 4 }}
|
||||
12
.github/helm/affine/charts/graphql/templates/serviceaccount.yaml
vendored
Normal file
12
.github/helm/affine/charts/graphql/templates/serviceaccount.yaml
vendored
Normal 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 }}
|
||||
15
.github/helm/affine/charts/graphql/templates/tests/test-connection.yaml
vendored
Normal file
15
.github/helm/affine/charts/graphql/templates/tests/test-connection.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "graphql.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "graphql.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "graphql.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
||||
69
.github/helm/affine/charts/graphql/values.yaml
vendored
Normal file
69
.github/helm/affine/charts/graphql/values.yaml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: ghcr.io/toeverything/affine-graphql
|
||||
pullPolicy: IfNotPresent
|
||||
tag: ''
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ''
|
||||
fullnameOverride: ''
|
||||
# map to NODE_ENV environment variable
|
||||
env: 'production'
|
||||
database:
|
||||
user: 'postgres'
|
||||
url: 'pg-postgresql'
|
||||
port: '5432'
|
||||
name: 'affine'
|
||||
app:
|
||||
# AFFINE_SERVER_SUB_PATH
|
||||
path: ''
|
||||
# AFFINE_SERVER_HOST
|
||||
host: '0.0.0.0'
|
||||
jwt:
|
||||
secretName: jwt-private-key
|
||||
# base64 encoded ecdsa private key
|
||||
privateKey: ''
|
||||
objectStorage:
|
||||
r2:
|
||||
enabled: false
|
||||
secretName: r2
|
||||
accountId: ''
|
||||
accessKeyId: ''
|
||||
secretAccessKey: ''
|
||||
bucket: ''
|
||||
oauth:
|
||||
google:
|
||||
enabled: false
|
||||
secretName: oauth-google
|
||||
clientId: ''
|
||||
clientSecret: ''
|
||||
github:
|
||||
enabled: false
|
||||
secretName: oauth-github
|
||||
clientId: ''
|
||||
clientSecret: ''
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
annotations: {}
|
||||
name: 'affine-graphql'
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 2000
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: '2000m'
|
||||
memory: 4Gi
|
||||
requests:
|
||||
cpu: '1000m'
|
||||
memory: 2Gi
|
||||
|
||||
probe:
|
||||
initialDelaySeconds: 20
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
23
.github/helm/affine/charts/web/.helmignore
vendored
Normal file
23
.github/helm/affine/charts/web/.helmignore
vendored
Normal 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/
|
||||
6
.github/helm/affine/charts/web/Chart.yaml
vendored
Normal file
6
.github/helm/affine/charts/web/Chart.yaml
vendored
Normal 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"
|
||||
16
.github/helm/affine/charts/web/templates/NOTES.txt
vendored
Normal file
16
.github/helm/affine/charts/web/templates/NOTES.txt
vendored
Normal 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 }}
|
||||
62
.github/helm/affine/charts/web/templates/_helpers.tpl
vendored
Normal file
62
.github/helm/affine/charts/web/templates/_helpers.tpl
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
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 }}
|
||||
{{- 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 }}
|
||||
57
.github/helm/affine/charts/web/templates/deployment.yaml
vendored
Normal file
57
.github/helm/affine/charts/web/templates/deployment.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
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 }}
|
||||
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 }}
|
||||
15
.github/helm/affine/charts/web/templates/service.yaml
vendored
Normal file
15
.github/helm/affine/charts/web/templates/service.yaml
vendored
Normal 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 }}
|
||||
12
.github/helm/affine/charts/web/templates/serviceaccount.yaml
vendored
Normal file
12
.github/helm/affine/charts/web/templates/serviceaccount.yaml
vendored
Normal 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 }}
|
||||
15
.github/helm/affine/charts/web/templates/tests/test-connection.yaml
vendored
Normal file
15
.github/helm/affine/charts/web/templates/tests/test-connection.yaml
vendored
Normal 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
|
||||
37
.github/helm/affine/charts/web/values.yaml
vendored
Normal file
37
.github/helm/affine/charts/web/values.yaml
vendored
Normal 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
|
||||
62
.github/helm/affine/templates/_helpers.tpl
vendored
Normal file
62
.github/helm/affine/templates/_helpers.tpl
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "affine.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 "affine.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 "affine.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "affine.labels" -}}
|
||||
helm.sh/chart: {{ include "affine.chart" . }}
|
||||
{{ include "affine.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "affine.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "affine.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "affine.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "affine.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
64
.github/helm/affine/templates/ingress.yaml
vendored
Normal file
64
.github/helm/affine/templates/ingress.yaml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "affine.fullname" . -}}
|
||||
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "affine.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: "{{ .Values.ingress.host }}"
|
||||
http:
|
||||
paths:
|
||||
- path: /graphql
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: affine-graphql
|
||||
port:
|
||||
number: {{ .Values.graphql.service.port }}
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: affine-graphql
|
||||
port:
|
||||
number: {{ .Values.graphql.service.port }}
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: affine-web
|
||||
port:
|
||||
number: {{ .Values.web.service.port }}
|
||||
|
||||
{{- end }}
|
||||
17
.github/helm/affine/values.yaml
vendored
Normal file
17
.github/helm/affine/values.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ''
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
host: affine.pro
|
||||
tls: []
|
||||
|
||||
graphql:
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 3000
|
||||
|
||||
web:
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
60
.github/helm/deployment_guide.md
vendored
Normal file
60
.github/helm/deployment_guide.md
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
# Cluster Deployment Guide
|
||||
|
||||
This document provides a step-by-step guide for developers on how to deploy services in a Kubernetes cluster. The following content assumes that the reader already has a basic understanding of Kubernetes concepts and operations.
|
||||
|
||||
### 1. Configure Service Mesh (Optional)
|
||||
|
||||
In the Kubernetes cluster, we optionally use Service Mesh (like Istio and Anthos Service Mesh) to manage the network interactions of microservices. If Service Mesh is already deployed on your cluster or do not need to use the service network, you can skip this step. In this step, we assume that you are using Google Kubernetes Engine (GKE) and have already installed Anthos Service Mesh on your cluster, if you wish to use another Ingress Controller, please refer to the relevant documentation.
|
||||
|
||||
To configure your kubectl context to interact with your Kubernetes cluster using the gcloud tool, you need to execute the following commands:
|
||||
|
||||
```sh
|
||||
export CLUSTER_NAME=your_cluster_name
|
||||
export REGION=your_cluster_region
|
||||
export PROJECT=your_project_id
|
||||
gcloud container clusters get-credentials $CLUSTER_NAME --region $REGION --project $PROJECT
|
||||
```
|
||||
|
||||
In this command, you should replace `CLUSTER_NAME`, `REGION` and `PROJECT` with the actual name, region and project id of your Kubernetes cluster. This command retrieves the access credentials for your Kubernetes cluster and automatically configures kubectl to use these credentials.
|
||||
|
||||
Now, to inject Service Mesh for a specific Namespace, first, set the environment variable `NAMESPACE` that should correspond to your target Kubernetes Namespace. In this example, we use `prod` as the target Namespace:
|
||||
|
||||
```sh
|
||||
export NAMESPACE=prod
|
||||
```
|
||||
|
||||
Then, we label the Namespace which will enable Istio to automatically inject the sidecar container for all new Pods under this Namespace:
|
||||
|
||||
```sh
|
||||
kubectl label namespace $NAMESPACE istio-injection- istio.io/rev=asm-managed --overwrite
|
||||
```
|
||||
|
||||
Finally, we trigger the Kubernetes Deployment restart mechanism to allow existing Pods to also obtain sidecar container injection:
|
||||
|
||||
```sh
|
||||
kubectl rollout restart deployment -n $NAMESPACE
|
||||
```
|
||||
|
||||
### 2. Deploying the Application
|
||||
|
||||
Next, we will deploy our application in the Kubernetes cluster through Helm. First, set relevant environment variables:
|
||||
|
||||
```sh
|
||||
export NAMESPACE=prod
|
||||
export RELEASE=affine-cloud-prod
|
||||
export PATH=.github/helm/affine-cloud
|
||||
```
|
||||
|
||||
- `NAMESPACE` should be consistent with the first step, indicating your target Kubernetes Namespace.
|
||||
- `RELEASE` is the name of your Helm release.
|
||||
- `PATH` is the location of your Helm chart in your file system.
|
||||
|
||||
Finally, use the `helm upgrade --install` command to deploy or upgrade your application:
|
||||
|
||||
```sh
|
||||
helm upgrade --namespace $NAMESPACE --create-namespace --install $RELEASE $PATH
|
||||
```
|
||||
|
||||
This command creates (if it doesn't already exist) and deploys your Helm chart in the specified Namespace. If the release already exists, it will be upgraded.
|
||||
|
||||
The above are the complete steps for deploying an application in a Kubernetes cluster. Make sure all prerequisites are met before deploying, and also ensure that you have the correct permissions for operations in Kubernetes.
|
||||
16
.github/labeler.yml
vendored
16
.github/labeler.yml
vendored
@@ -8,14 +8,20 @@ test:
|
||||
- '**/tests/**/*'
|
||||
- '**/__tests__/**/*'
|
||||
|
||||
plugin:copilot:
|
||||
- 'plugins/copilot/**/*'
|
||||
|
||||
mod:dev:
|
||||
- 'scripts/**/*'
|
||||
- 'packages/cli/**/*'
|
||||
- 'packages/debug/**/*'
|
||||
|
||||
mod:plugin:
|
||||
- 'plugins/**/*'
|
||||
|
||||
plugin:bookmark-block:
|
||||
- 'plugins/bookmark-block/**/*'
|
||||
|
||||
plugin:copilot:
|
||||
- 'plugins/copilot/**/*'
|
||||
|
||||
mod:plugin-infra:
|
||||
- 'packages/plugin-infra/**/*'
|
||||
|
||||
@@ -29,6 +35,10 @@ mod:hooks: 'packages/hooks/**/*'
|
||||
|
||||
mod:component: 'packages/component/**/*'
|
||||
|
||||
mod:storage: 'packages/storage/**/*'
|
||||
|
||||
mod:native: 'packages/native/**/*'
|
||||
|
||||
mod:store:
|
||||
- 'packages/jotai/**/*'
|
||||
- '**/atoms/**/*'
|
||||
|
||||
210
.github/workflows/build.yml
vendored
210
.github/workflows/build.yml
vendored
@@ -13,6 +13,7 @@ on:
|
||||
- '!.github/actions/build-rust/action.yml'
|
||||
- '!.github/actions/setup-node/action.yml'
|
||||
pull_request:
|
||||
merge_group:
|
||||
branches:
|
||||
- master
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
@@ -26,6 +27,7 @@ on:
|
||||
|
||||
env:
|
||||
DEBUG: napi:*
|
||||
BUILD_TYPE: canary
|
||||
APP_NAME: affine
|
||||
COVERAGE: true
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
@@ -41,12 +43,27 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Run checks
|
||||
with:
|
||||
electron-install: false
|
||||
- name: Run i18n codegen
|
||||
run: yarn i18n-codegen gen
|
||||
- name: Run Type Check
|
||||
run: yarn typecheck
|
||||
- name: Run ESLint
|
||||
run: yarn lint:eslint --max-warnings=0
|
||||
- name: Run Prettier
|
||||
# Set nmMode in `actions/setup-node` will modify the .yarnrc.yml
|
||||
run: |
|
||||
yarn i18n-codegen gen
|
||||
yarn typecheck
|
||||
yarn lint --max-warnings=0
|
||||
yarn circular
|
||||
git checkout .yarnrc.yml
|
||||
yarn lint:prettier
|
||||
- name: Run circular
|
||||
run: yarn circular
|
||||
- name: Upload server dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: server-dist
|
||||
path: ./apps/server/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-docs:
|
||||
name: Build Docs
|
||||
@@ -57,6 +74,8 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
- run: yarn nx build @affine/docs
|
||||
env:
|
||||
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
@@ -70,6 +89,8 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
- run: yarn nx build @affine/storybook
|
||||
env:
|
||||
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
@@ -77,22 +98,13 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storybook
|
||||
path: ./packages/storybook/storybook-static
|
||||
path: ./apps/storybook/storybook-static
|
||||
if-no-files-found: error
|
||||
|
||||
build-web:
|
||||
name: Build @affine/web
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
env:
|
||||
API_SERVER_PROFILE: local
|
||||
ENABLE_DEBUG_PAGE: 1
|
||||
ENABLE_PLUGIN: true
|
||||
ENABLE_ALL_PAGE_FILTER: true
|
||||
ENABLE_LEGACY_PROVIDER: true
|
||||
ENABLE_PRELOADING: false
|
||||
ENABLE_NEW_SETTING_MODAL: false
|
||||
ENABLE_SQLITE_PROVIDER: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -101,34 +113,6 @@ jobs:
|
||||
- name: Build Web
|
||||
run: yarn nx build @affine/web
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: next-js
|
||||
path: ./apps/web/.next
|
||||
if-no-files-found: error
|
||||
|
||||
build-web-desktop:
|
||||
name: Build @affine/web (Desktop)
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
env:
|
||||
API_SERVER_PROFILE: affine
|
||||
ENABLE_DEBUG_PAGE: 1
|
||||
ENABLE_PLUGIN: true
|
||||
ENABLE_ALL_PAGE_FILTER: true
|
||||
ENABLE_LEGACY_PROVIDER: false
|
||||
ENABLE_PRELOADING: false
|
||||
ENABLE_NEW_SETTING_MODAL: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Export static resources
|
||||
run: yarn nx export @affine/web
|
||||
|
||||
- name: Upload static resources artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: next-js-static
|
||||
@@ -174,10 +158,24 @@ jobs:
|
||||
working-directory: apps/server
|
||||
env:
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
- name: Build Storage
|
||||
run: yarn build:storage
|
||||
- name: Run server tests
|
||||
run: yarn nx test:coverage @affine/server
|
||||
run: yarn test:coverage
|
||||
working-directory: apps/server
|
||||
env:
|
||||
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
- name: Upload storage.node
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./packages/storage/storage.node
|
||||
if-no-files-found: error
|
||||
- name: Upload server test coverage results
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
@@ -185,7 +183,7 @@ jobs:
|
||||
files: ./apps/server/.coverage/lcov.info
|
||||
flags: server-test
|
||||
name: affine
|
||||
fail_ci_if_error: true
|
||||
fail_ci_if_error: false
|
||||
|
||||
storybook-test:
|
||||
name: Storybook Test
|
||||
@@ -198,13 +196,14 @@ jobs:
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download storybook artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storybook
|
||||
path: ./packages/storybook/storybook-static
|
||||
path: ./apps/storybook/storybook-static
|
||||
- name: Run storybook tests
|
||||
working-directory: ./packages/storybook
|
||||
working-directory: ./apps/storybook
|
||||
run: |
|
||||
yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"
|
||||
|
||||
@@ -214,18 +213,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4]
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
environment: development
|
||||
needs: [build-web, build-storybook]
|
||||
services:
|
||||
octobase:
|
||||
image: ghcr.io/toeverything/cloud-self-hosted:nightly-latest
|
||||
ports:
|
||||
- 3000:3000
|
||||
env:
|
||||
SIGN_KEY: 'test123'
|
||||
RUST_LOG: 'debug'
|
||||
JWST_DEV: '1'
|
||||
needs: build-web
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -233,24 +223,16 @@ jobs:
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: next-js
|
||||
path: ./apps/web/.next
|
||||
|
||||
- name: Download storybook artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storybook
|
||||
path: ./packages/storybook/storybook-static
|
||||
|
||||
- name: Wait for Octobase Ready
|
||||
run: |
|
||||
node ./scripts/wait-3000-healthz.mjs
|
||||
name: next-js-static
|
||||
path: ./apps/web/out
|
||||
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||
working-directory: tests/affine-local
|
||||
env:
|
||||
COVERAGE: true
|
||||
|
||||
@@ -264,7 +246,7 @@ jobs:
|
||||
files: ./.coverage/lcov.info
|
||||
flags: e2etest
|
||||
name: affine
|
||||
fail_ci_if_error: true
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
@@ -278,6 +260,7 @@ jobs:
|
||||
name: E2E Migration Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: [build-web]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -285,6 +268,13 @@ jobs:
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
|
||||
- name: Download next static
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: next-js-static
|
||||
path: ./apps/web/out
|
||||
|
||||
- name: Unzip
|
||||
run: yarn unzip
|
||||
@@ -294,6 +284,18 @@ jobs:
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: ./tests/affine-legacy/0.7.0-canary.18
|
||||
|
||||
- name: Run vitest
|
||||
run: yarn test
|
||||
working-directory: ./tests/affine-legacy/0.7.0-canary.18
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-migration
|
||||
path: ./tests/affine-legacy/0.7.0-canary.18/test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
desktop-test:
|
||||
name: Desktop Test
|
||||
runs-on: ${{ matrix.spec.os }}
|
||||
@@ -331,13 +333,14 @@ jobs:
|
||||
target: x86_64-pc-windows-msvc,
|
||||
test: true,
|
||||
}
|
||||
needs: [build-web-desktop]
|
||||
needs: [build-web]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
hard-link-nm: false
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
@@ -349,13 +352,18 @@ jobs:
|
||||
run: yarn nx test @affine/monorepo
|
||||
env:
|
||||
NATIVE_TEST: 'true'
|
||||
- name: Build layers
|
||||
run: yarn workspace @affine/electron build
|
||||
|
||||
- name: Download static resource artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: next-js-static
|
||||
path: ./apps/electron/resources/web-static
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn workspace @affine/electron build
|
||||
|
||||
- name: Run desktop tests
|
||||
if: ${{ matrix.spec.test && matrix.spec.os == 'ubuntu-latest' }}
|
||||
@@ -369,6 +377,17 @@ jobs:
|
||||
env:
|
||||
COVERAGE: true
|
||||
|
||||
- name: Make bundle
|
||||
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
|
||||
run: yarn workspace @affine/electron make --platform=darwin --arch=arm64
|
||||
|
||||
- name: Bundle output check
|
||||
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
|
||||
run: |
|
||||
./scripts/unzip-macos-arm64.sh
|
||||
yarn ts-node-esm ./scripts/macos-arm64-output-check.mts
|
||||
working-directory: apps/electron
|
||||
|
||||
- name: Collect code coverage report
|
||||
if: ${{ matrix.spec.test }}
|
||||
run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
|
||||
@@ -381,7 +400,7 @@ jobs:
|
||||
files: ./.coverage/lcov.info
|
||||
flags: e2etest-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
|
||||
name: affine
|
||||
fail_ci_if_error: true
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
@@ -395,20 +414,12 @@ jobs:
|
||||
name: Unit Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
services:
|
||||
octobase:
|
||||
image: ghcr.io/toeverything/cloud-self-hosted:nightly-latest
|
||||
ports:
|
||||
- 3000:3000
|
||||
env:
|
||||
SIGN_KEY: 'test123'
|
||||
RUST_LOG: 'debug'
|
||||
JWST_DEV: '1'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
|
||||
- name: Unit Test
|
||||
run: yarn nx test:coverage @affine/monorepo
|
||||
@@ -420,13 +431,15 @@ jobs:
|
||||
files: ./.coverage/store/lcov.info
|
||||
flags: unittest
|
||||
name: affine
|
||||
fail_ci_if_error: true
|
||||
fail_ci_if_error: false
|
||||
|
||||
build-docker:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
name: Build Docker
|
||||
needs:
|
||||
- build-web-desktop
|
||||
- lint
|
||||
- desktop-test
|
||||
- server-test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -435,6 +448,16 @@ jobs:
|
||||
with:
|
||||
name: next-js-static
|
||||
path: ./apps/web/out
|
||||
- name: Download server dist
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: server-dist
|
||||
path: ./apps/server/dist
|
||||
- name: Download storage.node
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./apps/server
|
||||
- name: Setup Git short hash
|
||||
run: |
|
||||
echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
|
||||
@@ -460,14 +483,21 @@ jobs:
|
||||
file: .github/deployment/front/Dockerfile
|
||||
tags: ghcr.io/toeverything/affine-front:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-front:latest
|
||||
|
||||
# setup node without cache configuration
|
||||
# Prisma cache is not compatible with docker build cache
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
package-install: false
|
||||
node-version-file: '.nvmrc'
|
||||
registry-url: https://npm.pkg.github.com
|
||||
scope: '@toeverything'
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: yarn workspaces focus @affine/server --production
|
||||
|
||||
- name: Generate Prisma client
|
||||
run: yarn workspace @affine/server prisma generate
|
||||
|
||||
- name: Build graphql Dockerfile
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
|
||||
18
.github/workflows/cancel.yml
vendored
Normal file
18
.github/workflows/cancel.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Cancel
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
cancel:
|
||||
name: 'Cancel Previous Runs'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
# See https://api.github.com/repos/toeverything/AFFiNE/actions/workflows
|
||||
workflow_id: 44038251, 61883931
|
||||
access_token: ${{ github.token }}
|
||||
1
.github/workflows/codeql.yml
vendored
1
.github/workflows/codeql.yml
vendored
@@ -15,6 +15,7 @@ on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
merge_group:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ name: Release Charts
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths:
|
||||
- '.github/helm/**/Chart.yml'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -39,19 +39,24 @@ jobs:
|
||||
- name: Package 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"
|
||||
mkdir -p .cr-index
|
||||
owner=$(cut -d '/' -f 1 <<< '${{ github.repository }}')
|
||||
repo=helm-charts
|
||||
git_hash=$(git rev-parse HEAD)
|
||||
echo $git_hash
|
||||
|
||||
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 }}' \
|
||||
13
.github/workflows/nightly-build.yml
vendored
13
.github/workflows/nightly-build.yml
vendored
@@ -11,6 +11,8 @@ on:
|
||||
- README.md
|
||||
- .github/**
|
||||
- '!.github/workflows/nightly-build.yml'
|
||||
- '!.github/actions/build-rust/action.yml'
|
||||
- '!.github/actions/setup-rust/action.yml'
|
||||
- '!.github/actions/setup-node/action.yml'
|
||||
|
||||
permissions:
|
||||
@@ -32,7 +34,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
outputs:
|
||||
version: 0.0.0-${{ steps.version.outputs.version }}
|
||||
version: 0.0.0-internal.${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: toeverything/set-build-version@latest
|
||||
@@ -58,10 +60,6 @@ jobs:
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
API_SERVER_PROFILE: prod
|
||||
ENABLE_TEST_PROPERTIES: false
|
||||
ENABLE_BOOKMARK_OPERATION: true
|
||||
ENABLE_SQLITE_PROVIDER: false
|
||||
RELEASE_VERSION: ${{ needs.set-build-version.outputs.version }}
|
||||
|
||||
- name: Upload Artifact (web-static)
|
||||
@@ -125,7 +123,10 @@ jobs:
|
||||
name: before-make-web-static
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build layers
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn workspace @affine/electron build
|
||||
|
||||
- name: Signing By Apple Developer ID
|
||||
|
||||
54
.github/workflows/nx.yml
vendored
Normal file
54
.github/workflows/nx.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: NX
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
- README.md
|
||||
- .github/**
|
||||
- '!.github/workflows/nx.yml'
|
||||
- '!.github/actions/build-rust/action.yml'
|
||||
- '!.github/actions/setup-node/action.yml'
|
||||
pull_request:
|
||||
merge_group:
|
||||
branches:
|
||||
- master
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
- README.md
|
||||
- .github/**
|
||||
- '!.github/workflows/nx.yml'
|
||||
- '!.github/actions/build-rust/action.yml'
|
||||
- '!.github/actions/setup-node/action.yml'
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Nx Cloud - Main Job
|
||||
uses: nrwl/ci/.github/workflows/nx-cloud-main.yml@v0.13.0
|
||||
with:
|
||||
runs-on: macos-latest
|
||||
main-branch-name: master
|
||||
number-of-agents: 5
|
||||
init-commands: |
|
||||
yarn exec nx-cloud start-ci-run --stop-agents-after="build" --agent-count=5
|
||||
environment-variables: |
|
||||
BUILD_TYPE=canary
|
||||
# parallel-commands: |
|
||||
# yarn exec nx-cloud record -- yarn exec nx format:check
|
||||
parallel-commands-on-agents: |
|
||||
yarn exec nx affected --target=build --parallel=5
|
||||
timeout: 60
|
||||
|
||||
agents:
|
||||
name: Nx Cloud - Agents
|
||||
uses: nrwl/ci/.github/workflows/nx-cloud-agents.yml@v0.13.0
|
||||
with:
|
||||
runs-on: macos-latest
|
||||
number-of-agents: 5
|
||||
environment-variables: |
|
||||
BUILD_TYPE=canary
|
||||
timeout: 60
|
||||
51
.github/workflows/release-desktop-app.yml
vendored
51
.github/workflows/release-desktop-app.yml
vendored
@@ -1,6 +1,9 @@
|
||||
name: Release Desktop App
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-canary.[0-9]+'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
@@ -28,14 +31,8 @@ permissions:
|
||||
contents: write
|
||||
security-events: write
|
||||
|
||||
concurrency:
|
||||
# The concurrency group contains the workflow name and the branch name for
|
||||
# pull requests or the commit hash for any other events.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
BUILD_TYPE: ${{ github.event.inputs.build-type }}
|
||||
BUILD_TYPE: ${{ github.event.inputs.build-type || (github.ref_type == 'tag' && contains(github.ref, 'canary') && 'canary') }}
|
||||
DEBUG: napi:*
|
||||
APP_NAME: affine
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
@@ -43,22 +40,31 @@ env:
|
||||
jobs:
|
||||
before-make:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ github.ref_name == 'master' && 'production' || 'development' }}
|
||||
environment: production
|
||||
outputs:
|
||||
RELEASE_VERSION: ${{ steps.get-canary-version.outputs.RELEASE_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Get canary version
|
||||
id: get-canary-version
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
run: |
|
||||
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
||||
PACKAGE_VERSION=$(node -p "require('./apps/electron/package.json').version")
|
||||
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
|
||||
echo "Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
echo "RELEASE_VERSION=$(node -p "require('./apps/electron/package.json').version")" >> $GITHUB_OUTPUT
|
||||
- name: generate-assets
|
||||
run: yarn workspace @affine/electron generate-assets
|
||||
env:
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
API_SERVER_PROFILE: prod
|
||||
ENABLE_TEST_PROPERTIES: false
|
||||
ENABLE_BOOKMARK_OPERATION: true
|
||||
ENABLE_SQLITE_PROVIDER: false
|
||||
RELEASE_VERSION: ${{ github.event.inputs.version }}
|
||||
RELEASE_VERSION: ${{ github.event.inputs.version || steps.get-canary-version.outputs.RELEASE_VERSION }}
|
||||
|
||||
- name: Upload Artifact (web-static)
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -67,7 +73,7 @@ jobs:
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
make-distribution:
|
||||
environment: ${{ github.ref_name == 'master' && 'production' || 'development' }}
|
||||
environment: production
|
||||
strategy:
|
||||
# all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
|
||||
matrix:
|
||||
@@ -117,7 +123,10 @@ jobs:
|
||||
name: before-make-web-static
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build layers
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn workspace @affine/electron build
|
||||
|
||||
- name: Signing By Apple Developer ID
|
||||
@@ -158,7 +167,7 @@ jobs:
|
||||
path: builds
|
||||
|
||||
release:
|
||||
needs: make-distribution
|
||||
needs: [before-make, make-distribution]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -191,16 +200,16 @@ jobs:
|
||||
cp ./apps/electron/scripts/generate-yml.js .
|
||||
node generate-yml.js
|
||||
env:
|
||||
RELEASE_VERSION: ${{ github.event.inputs.version }}
|
||||
RELEASE_VERSION: ${{ github.event.inputs.version || needs.before-make.outputs.RELEASE_VERSION }}
|
||||
- name: Create Release Draft
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
with:
|
||||
name: Desktop APP ${{ github.event.inputs.version }}
|
||||
body: 'TODO: Add release notes here'
|
||||
draft: ${{ github.event.inputs.is-draft }}
|
||||
prerelease: ${{ github.event.inputs.is-pre-release }}
|
||||
name: ${{ github.event.inputs.version || needs.before-make.outputs.RELEASE_VERSION }}
|
||||
body: ''
|
||||
draft: ${{ github.event.inputs.is-draft || true }}
|
||||
prerelease: ${{ github.event.inputs.is-pre-release || needs.before-make.outputs.version }}
|
||||
files: |
|
||||
./VERSION
|
||||
./*.zip
|
||||
|
||||
22
.github/workflows/workers.yml
vendored
Normal file
22
.github/workflows/workers.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Deploy Cloudflare Worker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- packages/workers/**
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
environment: production
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Publish
|
||||
uses: cloudflare/wrangler-action@2.0.0
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
workingDirectory: 'packages/workers'
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -60,9 +60,10 @@ out/
|
||||
storybook-static
|
||||
i18n-generated.ts
|
||||
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
test-results
|
||||
playwright-report
|
||||
playwright/.cache
|
||||
download
|
||||
|
||||
# Cache
|
||||
.eslintcache
|
||||
|
||||
@@ -2,7 +2,22 @@
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# check lockfile is up to date
|
||||
yarn install
|
||||
yarn install --mode=skip-build --inline-builds --immutable
|
||||
|
||||
# build infra code
|
||||
yarn -T run build:infra
|
||||
|
||||
# generate prisma client type
|
||||
yarn workspace @affine/server prisma generate
|
||||
|
||||
# generate i18n
|
||||
yarn i18n-codegen gen
|
||||
|
||||
# lint staged files
|
||||
yarn exec lint-staged
|
||||
|
||||
# type check
|
||||
yarn typecheck
|
||||
|
||||
# circular dependency check
|
||||
yarn circular
|
||||
|
||||
1
.npmrc
1
.npmrc
@@ -1,2 +1,3 @@
|
||||
shell-emulator=true
|
||||
electron_mirror="https://cdn.npmmirror.com/binaries/electron/"
|
||||
engine-strict=true
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
target
|
||||
lib
|
||||
test-results
|
||||
packages/i18n/src/i18n-generated.ts
|
||||
packages/graphql/src/graphql/index.ts
|
||||
.next
|
||||
out
|
||||
dist
|
||||
.yarn
|
||||
tests/affine-legacy/0.7.0-canary.18/static
|
||||
.github/helm
|
||||
_next
|
||||
storybook-static
|
||||
|
||||
19
README.md
19
README.md
@@ -2,13 +2,13 @@
|
||||
|
||||
<h1 style="border-bottom: none">
|
||||
<b><a href="https://affine.pro">AFFiNE.PRO</a></b><br />
|
||||
The Next-Gen Collaborative Knowledge Base
|
||||
Write, Draw and Plan All at Once
|
||||
<br>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together.<br />
|
||||
Privacy first, open-source, customizable and ready to use - a free replacement for Notion & Miro. <br />
|
||||
One hyper-fused platform for wildly creative minds. <br />
|
||||
A privacy-focussed, local-first, open-source, and ready-to-use alternative for Notion & Miro.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -60,7 +60,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
|
||||
|
||||
<br />
|
||||
<div align="center">
|
||||
<em>See docs, canvas and tables are hyper merged with AFFiNE - just like the word affine (əˈfʌɪn | a-fine).</em>
|
||||
<em>Docs, canvas and tables are hyper-merged with AFFiNE - just like the word affine (əˈfʌɪn | a-fine).</em>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
@@ -123,6 +123,8 @@ If you have questions, you are welcome to contact us. One of the best places to
|
||||
## Plugins
|
||||
|
||||
> Plugins are a way to extend the functionality of AFFiNE.
|
||||
>
|
||||
> (Currently, plugins are under heavy development, and the SDK is not yet available.)
|
||||
|
||||
| Name | |
|
||||
| ------------------------------------------------ | ----------------------------------------- |
|
||||
@@ -135,7 +137,7 @@ We would also like to give thanks to open-source projects that make AFFiNE possi
|
||||
|
||||
- [BlockSuite](https://github.com/toeverything/BlockSuite) - 💠 BlockSuite is the open-source collaborative editor project behind AFFiNE.
|
||||
- [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) & [Yrs](https://github.com/y-crdt/y-crdt) - 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.
|
||||
- [Electron](https://github.com/electron/electron) - Build cross-platform desktop apps with JavaScript, HTML, and CSS.
|
||||
- [React](https://github.com/facebook/react) - View layer support and web GUI framework.
|
||||
- [Rust](https://github.com/rust-lang/rust) - High performance language that extends the ability and availability of our real-time backend, OctoBase.
|
||||
@@ -156,7 +158,8 @@ We would like to express our gratitude to all the individuals who have already c
|
||||
|
||||
## Self-Host
|
||||
|
||||
Get started with Docker and deploy your own feature-rich, restriction-free deployment of AFFiNE - check the [latest packages].
|
||||
Get started with Docker and deploy your own feature-rich, restriction-free deployment of AFFiNE.
|
||||
We are working hard to get this updated to the latest version, you can keep an eye on the [latest packages].
|
||||
|
||||
## Hiring
|
||||
|
||||
@@ -197,7 +200,7 @@ See [LICENSE] for details.
|
||||
[rust-version-icon]: https://img.shields.io/badge/Rust-1.70.0-dea584
|
||||
[stars-icon]: https://img.shields.io/github/stars/toeverything/AFFiNE.svg?style=flat&logo=github&colorB=red&label=stars
|
||||
[codecov]: https://codecov.io/gh/toeverything/affine/branch/master/graphs/badge.svg?branch=master
|
||||
[node-version-icon]: https://img.shields.io/badge/node-%3E=18.16.0-success
|
||||
[node-version-icon]: https://img.shields.io/badge/node-%3E=18.16.1-success
|
||||
[typescript-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/affine/dev/typescript
|
||||
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/affine/dev/react?color=rgb%2897%2C%20218%2C%20251%29
|
||||
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=apps%2Fweb%2Fpackage.json&color=rgb(97%2C228%2C251)
|
||||
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=apps%2Fweb%2Fpackage.json&label=blocksuite
|
||||
|
||||
25
apps/README.md
Normal file
25
apps/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Apps structure
|
||||
|
||||
> This is the structure of the `apps` directory.
|
||||
|
||||
## docs
|
||||
|
||||
AFFiNE Developer Documentation using [waku](https://github.com/dai-shi/waku).
|
||||
|
||||
## electron
|
||||
|
||||
> `web` needs to be built before electron.
|
||||
|
||||
AFFiNE Desktop (macOS, Linux and Windows Distribution) using [Electron](https://www.electronjs.org/).
|
||||
|
||||
## server
|
||||
|
||||
Server using [Nest.js](https://nestjs.com/).
|
||||
|
||||
## storybook
|
||||
|
||||
Storybook using [Storybook](https://storybook.js.org/).
|
||||
|
||||
## web
|
||||
|
||||
AFFiNE Core Application using [React.js](https://reactjs.org/).
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/docs",
|
||||
"version": "0.7.0-canary.20",
|
||||
"version": "0.7.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -10,26 +10,26 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230627165830-836e6fd1-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230627165830-836e6fd1-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230627165830-836e6fd1-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230627165830-836e6fd1-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230627165830-836e6fd1-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230627165830-836e6fd1-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230717055529-79180930-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230717055529-79180930-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230717055529-79180930-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230717055529-79180930-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230717055529-79180930-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230717055529-79180930-nightly",
|
||||
"express": "^4.18.2",
|
||||
"jotai": "^2.2.1",
|
||||
"react": "18.3.0-canary-8ec962d82-20230623",
|
||||
"react-dom": "18.3.0-canary-8ec962d82-20230623",
|
||||
"react-server-dom-webpack": "18.3.0-canary-8ec962d82-20230623",
|
||||
"jotai": "^2.2.2",
|
||||
"react": "18.3.0-canary-1fdacbefd-20230630",
|
||||
"react-dom": "18.3.0-canary-1fdacbefd-20230630",
|
||||
"react-server-dom-webpack": "18.3.0-canary-1fdacbefd-20230630",
|
||||
"waku": "0.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@vanilla-extract/css": "^1.11.1",
|
||||
"@vanilla-extract/css": "^1.12.0",
|
||||
"@vanilla-extract/vite-plugin": "^3.8.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^5.1.3"
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
2
apps/electron/.gitignore
vendored
2
apps/electron/.gitignore
vendored
@@ -13,3 +13,5 @@ resources/web-static
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
dev.json
|
||||
|
||||
zip-out
|
||||
|
||||
@@ -140,3 +140,44 @@ test('affine onboarding button', async ({ page }) => {
|
||||
|
||||
expect(await onboardingModal.isVisible()).toEqual(false);
|
||||
});
|
||||
|
||||
test('windows only check', async ({ page }) => {
|
||||
const windowOnlyUI = page.locator('[data-platform-target=win32]');
|
||||
if (process.platform === 'win32') {
|
||||
await expect(windowOnlyUI).toBeVisible();
|
||||
} else {
|
||||
await expect(windowOnlyUI).not.toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('delete workspace', async ({ page }) => {
|
||||
await page.getByTestId('current-workspace').click();
|
||||
await page.getByTestId('add-or-new-workspace').click();
|
||||
await page.getByTestId('new-workspace').click();
|
||||
await page.getByTestId('create-workspace-default-location-button').click();
|
||||
await page.getByTestId('create-workspace-input').type('Delete Me');
|
||||
await page.getByTestId('create-workspace-create-button').click();
|
||||
await page.getByTestId('create-workspace-continue-button').click();
|
||||
await page.getByTestId('slider-bar-workspace-setting-button').click();
|
||||
await page.getByTestId('current-workspace-label').click();
|
||||
expect(await page.getByTestId('workspace-name-input').inputValue()).toBe(
|
||||
'Delete Me'
|
||||
);
|
||||
const contentElement = await page.getByTestId('setting-modal-content');
|
||||
const boundingBox = await contentElement.boundingBox();
|
||||
if (!boundingBox) {
|
||||
throw new Error('boundingBox is null');
|
||||
}
|
||||
await page.mouse.move(
|
||||
boundingBox.x + boundingBox.width / 2,
|
||||
boundingBox.y + boundingBox.height / 2
|
||||
);
|
||||
await page.mouse.wheel(0, 500);
|
||||
await page.getByTestId('delete-workspace-button').click();
|
||||
await page.getByTestId('delete-workspace-input').type('Delete Me');
|
||||
await page.getByTestId('delete-workspace-confirm-button').click();
|
||||
await page.waitForTimeout(1000);
|
||||
expect(await page.getByTestId('workspace-name').textContent()).toBe(
|
||||
'Demo Workspace'
|
||||
);
|
||||
});
|
||||
@@ -16,6 +16,8 @@ function generateUUID() {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
type RoutePath = 'setting';
|
||||
|
||||
export const test = base.extend<{
|
||||
page: Page;
|
||||
electronApp: ElectronApplication;
|
||||
@@ -24,9 +26,8 @@ export const test = base.extend<{
|
||||
appData: string;
|
||||
sessionData: string;
|
||||
};
|
||||
workspace: {
|
||||
// get current workspace
|
||||
current: () => Promise<any>; // todo: type
|
||||
router: {
|
||||
goto: (path: RoutePath) => Promise<void>;
|
||||
};
|
||||
}>({
|
||||
page: async ({ electronApp }, use) => {
|
||||
@@ -41,10 +42,6 @@ export const test = base.extend<{
|
||||
});
|
||||
});
|
||||
}
|
||||
const logFilePath = await page.evaluate(async () => {
|
||||
// @ts-expect-error
|
||||
return window.apis?.debug.logFilePath();
|
||||
});
|
||||
// wat for blocksuite to be loaded
|
||||
await page.waitForSelector('v-line');
|
||||
if (enableCoverage) {
|
||||
@@ -71,10 +68,6 @@ export const test = base.extend<{
|
||||
);
|
||||
}
|
||||
await page.close();
|
||||
if (logFilePath) {
|
||||
const logs = await fs.readFile(logFilePath, 'utf-8');
|
||||
console.log(logs);
|
||||
}
|
||||
},
|
||||
electronApp: async ({}, use) => {
|
||||
// a random id to avoid conflicts between tests
|
||||
@@ -124,14 +117,4 @@ export const test = base.extend<{
|
||||
});
|
||||
await use(appInfo);
|
||||
},
|
||||
workspace: async ({ page }, use) => {
|
||||
await use({
|
||||
current: async () => {
|
||||
return await page.evaluate(async () => {
|
||||
// @ts-expect-error
|
||||
return globalThis.currentWorkspace;
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -5,7 +5,7 @@ import fs from 'fs-extra';
|
||||
|
||||
import { test } from './fixture';
|
||||
|
||||
test.skip('check workspace has a DB file', async ({ appInfo, workspace }) => {
|
||||
test('check workspace has a DB file', async ({ appInfo, workspace }) => {
|
||||
const w = await workspace.current();
|
||||
const dbPath = path.join(
|
||||
appInfo.sessionData,
|
||||
@@ -19,9 +19,11 @@ test.skip('check workspace has a DB file', async ({ appInfo, workspace }) => {
|
||||
|
||||
test.skip('move workspace db file', async ({ page, appInfo, workspace }) => {
|
||||
const w = await workspace.current();
|
||||
const settingButton = page.getByTestId('slider-bar-workspace-setting-button');
|
||||
// goto settings
|
||||
await settingButton.click();
|
||||
await page.getByTestId('slider-bar-workspace-setting-button').click();
|
||||
await expect(page.getByTestId('setting-modal')).toBeVisible();
|
||||
|
||||
// goto workspace setting
|
||||
await page.getByTestId('workspace-list-item').click();
|
||||
|
||||
const tmpPath = path.join(appInfo.sessionData, w.id + '-tmp-dir');
|
||||
|
||||
@@ -42,21 +44,26 @@ test.skip('move workspace db file', async ({ page, appInfo, workspace }) => {
|
||||
expect(files.some(f => f.endsWith('.affine'))).toBe(true);
|
||||
});
|
||||
|
||||
test.skip('export then add', async ({ page, appInfo, workspace }) => {
|
||||
test('export then add', async ({ page, appInfo, workspace }) => {
|
||||
const w = await workspace.current();
|
||||
const settingButton = page.getByTestId('slider-bar-workspace-setting-button');
|
||||
// goto settings
|
||||
await settingButton.click();
|
||||
|
||||
await page.getByTestId('slider-bar-workspace-setting-button').click();
|
||||
await expect(page.getByTestId('setting-modal')).toBeVisible();
|
||||
|
||||
const originalId = w.id;
|
||||
|
||||
const newWorkspaceName = 'new-test-name';
|
||||
|
||||
// goto workspace setting
|
||||
await page.getByTestId('workspace-list-item').click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// change workspace name
|
||||
await page.getByTestId('workspace-name-input').fill(newWorkspaceName);
|
||||
await page.getByTestId('save-workspace-name').click();
|
||||
await page.waitForSelector('text="Update workspace name success"');
|
||||
await page.click('[data-tab-key="export"]');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const tmpPath = path.join(appInfo.sessionData, w.id + '-tmp.db');
|
||||
|
||||
@@ -73,10 +80,11 @@ test.skip('export then add', async ({ page, appInfo, workspace }) => {
|
||||
|
||||
expect(await fs.exists(tmpPath)).toBe(true);
|
||||
|
||||
await page.getByTestId('modal-close-button').click();
|
||||
|
||||
// add workspace
|
||||
// we are reusing the same db file so that we don't need to maintain one
|
||||
// in the codebase
|
||||
|
||||
await page.getByTestId('current-workspace').click();
|
||||
await page.getByTestId('add-or-new-workspace').click();
|
||||
|
||||
@@ -26,6 +26,8 @@ const arch =
|
||||
? process.argv[process.argv.indexOf('--arch') + 1]
|
||||
: process.arch;
|
||||
|
||||
const windowsIconUrl = `https://cdn.affine.pro/app-icons/icon_${buildType}.ico`;
|
||||
|
||||
/**
|
||||
* @type {import('@electron-forge/shared-types').ForgeConfig}
|
||||
*/
|
||||
@@ -95,6 +97,7 @@ module.exports = {
|
||||
config: {
|
||||
name: 'AFFiNE',
|
||||
setupIcon: icoPath,
|
||||
iconUrl: windowsIconUrl,
|
||||
loadingGif: './resources/icons/affine_installing.gif',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"private": true,
|
||||
"version": "0.7.0-canary.20",
|
||||
"version": "0.7.0",
|
||||
"author": "affine",
|
||||
"repository": {
|
||||
"url": "https://github.com/toeverything/AFFiNE",
|
||||
@@ -11,28 +11,25 @@
|
||||
"homepage": "https://github.com/toeverything/AFFiNE",
|
||||
"scripts": {
|
||||
"dev": "yarn cross-env DEV_SERVER_URL=http://localhost:8080 node scripts/dev.mjs",
|
||||
"watch": "yarn cross-env DEV_SERVER_URL=http://localhost:8080 node scripts/dev.mjs --watch",
|
||||
"prod": "yarn node scripts/dev.mjs",
|
||||
"build": "zx scripts/build-layers.mjs",
|
||||
"dev:prod": "yarn node scripts/dev.mjs",
|
||||
"build": "NODE_ENV=production zx scripts/build-layers.mjs",
|
||||
"generate-assets": "zx scripts/generate-assets.mjs",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"test": "DEBUG=pw:browser playwright test"
|
||||
"test": "DEBUG=pw:browser yarn -T run playwright test -c ./playwright.config.ts"
|
||||
},
|
||||
"config": {
|
||||
"forge": "./forge.config.js"
|
||||
},
|
||||
"main": "./dist/main.js",
|
||||
"exports": {
|
||||
"./scripts/plugins/build-plugins.mjs": "./scripts/plugins/build-plugins.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@affine-test/kit": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/native": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230627165830-836e6fd1-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230627165830-836e6fd1-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230627165830-836e6fd1-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230627165830-836e6fd1-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230717055529-79180930-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230717055529-79180930-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230717055529-79180930-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230717055529-79180930-nightly",
|
||||
"@electron-forge/cli": "^6.2.1",
|
||||
"@electron-forge/core": "^6.2.1",
|
||||
"@electron-forge/core-utils": "^6.2.1",
|
||||
@@ -50,10 +47,9 @@
|
||||
"electron-log": "^5.0.0-beta.24",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"esbuild": "^0.18.9",
|
||||
"esbuild": "^0.18.11",
|
||||
"fs-extra": "^11.1.1",
|
||||
"jotai": "^2.2.1",
|
||||
"playwright": "=1.33.0",
|
||||
"jotai": "^2.2.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"undici": "^5.22.1",
|
||||
"uuid": "^9.0.0",
|
||||
@@ -63,7 +59,7 @@
|
||||
"dependencies": {
|
||||
"@toeverything/plugin-infra": "workspace:*",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"electron-updater": "^5.3.0",
|
||||
"electron-updater": "^6.0.0",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.2",
|
||||
@@ -85,7 +81,6 @@
|
||||
"hoistingLimits": "workspaces"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"playwright": "*",
|
||||
"ts-node": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: './tests',
|
||||
testDir: './e2e',
|
||||
testIgnore: '**/lib/**',
|
||||
fullyParallel: true,
|
||||
timeout: process.env.CI ? 50_000 : 30_000,
|
||||
|
||||
24
apps/electron/project.json
Normal file
24
apps/electron/project.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"root": "apps/electron",
|
||||
"sourceRoot": "apps/electron/src",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "nx:run-script",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": ["@affine/bookmark-block"],
|
||||
"target": "build",
|
||||
"params": "ignore"
|
||||
},
|
||||
"^build"
|
||||
],
|
||||
"options": {
|
||||
"script": "build"
|
||||
},
|
||||
"outputs": ["{projectRoot}/dist"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
#!/usr/bin/env zx
|
||||
import 'zx/globals';
|
||||
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as esbuild from 'esbuild';
|
||||
|
||||
import { config, rootDir } from './common.mjs';
|
||||
import { config } from './common.mjs';
|
||||
|
||||
const NODE_ENV =
|
||||
process.env.NODE_ENV === 'development' ? 'development' : 'production';
|
||||
@@ -18,20 +15,10 @@ if (process.platform === 'win32') {
|
||||
|
||||
async function buildLayers() {
|
||||
const common = config();
|
||||
console.log('Build plugin infra');
|
||||
spawnSync('yarn', ['build'], {
|
||||
stdio: 'inherit',
|
||||
cwd: resolve(rootDir, './packages/plugin-infra'),
|
||||
});
|
||||
|
||||
console.log('Build plugins');
|
||||
await import('./plugins/build-plugins.mjs');
|
||||
|
||||
await esbuild.build(common.workers);
|
||||
await esbuild.build({
|
||||
...common.layers,
|
||||
define: {
|
||||
...common.define,
|
||||
'process.env.NODE_ENV': `"${NODE_ENV}"`,
|
||||
'process.env.BUILD_TYPE': `"${process.env.BUILD_TYPE || 'stable'}"`,
|
||||
},
|
||||
|
||||
@@ -15,16 +15,9 @@ const DEV_SERVER_URL = process.env.DEV_SERVER_URL;
|
||||
/** @type 'production' | 'development'' */
|
||||
const mode = (process.env.NODE_ENV = process.env.NODE_ENV || 'development');
|
||||
|
||||
// List of env that will be replaced by esbuild
|
||||
const ENV_MACROS = ['AFFINE_GOOGLE_CLIENT_ID', 'AFFINE_GOOGLE_CLIENT_SECRET'];
|
||||
|
||||
/** @return {{layers: import('esbuild').BuildOptions, workers: import('esbuild').BuildOptions}} */
|
||||
export const config = () => {
|
||||
const define = Object.fromEntries([
|
||||
...ENV_MACROS.map(key => [
|
||||
'process.env.' + key,
|
||||
JSON.stringify(process.env[key] ?? ''),
|
||||
]),
|
||||
['process.env.NODE_ENV', `"${mode}"`],
|
||||
['process.env.USE_WORKER', '"true"'],
|
||||
]);
|
||||
@@ -45,7 +38,12 @@ export const config = () => {
|
||||
bundle: true,
|
||||
target: `node${NODE_MAJOR_VERSION}`,
|
||||
platform: 'node',
|
||||
external: ['electron', 'electron-updater', '@toeverything/plugin-infra'],
|
||||
external: [
|
||||
'electron',
|
||||
'electron-updater',
|
||||
'@toeverything/plugin-infra',
|
||||
'yjs',
|
||||
],
|
||||
define: define,
|
||||
format: 'cjs',
|
||||
loader: {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
import { spawn } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path, { resolve } from 'node:path';
|
||||
import path from 'node:path';
|
||||
|
||||
import electronPath from 'electron';
|
||||
import * as esbuild from 'esbuild';
|
||||
import which from 'which';
|
||||
|
||||
import { config, electronDir, rootDir } from './common.mjs';
|
||||
import { config, electronDir } from './common.mjs';
|
||||
|
||||
// this means we don't spawn electron windows, mainly for testing
|
||||
const watchMode = process.argv.includes('--watch');
|
||||
@@ -68,14 +67,6 @@ function spawnOrReloadElectron() {
|
||||
}
|
||||
|
||||
const common = config();
|
||||
const yarnPath = which.sync('yarn');
|
||||
async function watchPlugins() {
|
||||
spawn(yarnPath, ['dev'], {
|
||||
stdio: 'inherit',
|
||||
cwd: resolve(rootDir, './packages/plugin-infra'),
|
||||
});
|
||||
await import('./plugins/dev-plugins.mjs');
|
||||
}
|
||||
|
||||
async function watchLayers() {
|
||||
return new Promise(async resolve => {
|
||||
@@ -134,7 +125,6 @@ async function watchWorkers() {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await watchPlugins();
|
||||
await watchLayers();
|
||||
await watchWorkers();
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ cd(repoRootDir);
|
||||
if (!process.env.SKIP_WEB_BUILD) {
|
||||
process.env.ENABLE_LEGACY_PROVIDER = 'false';
|
||||
await $`yarn nx build @affine/web`;
|
||||
await $`yarn nx export @affine/web`;
|
||||
|
||||
// step 1.5: amend sourceMappingURL to allow debugging in devtools
|
||||
await glob('**/*.{js,css}', { cwd: affineWebOutDir }).then(files => {
|
||||
|
||||
44
apps/electron/scripts/macos-arm64-output-check.mts
Normal file
44
apps/electron/scripts/macos-arm64-output-check.mts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { readdir } from 'node:fs/promises';
|
||||
|
||||
const outputRoot = fileURLToPath(
|
||||
new URL(
|
||||
'../zip-out/AFFiNE-canary.app/Contents/Resources/app',
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
const outputList = [
|
||||
[
|
||||
'dist',
|
||||
[
|
||||
'main.js',
|
||||
'helper.js',
|
||||
'preload.js',
|
||||
'affine.darwin-arm64.node',
|
||||
'plugins',
|
||||
'workers',
|
||||
],
|
||||
],
|
||||
['dist/plugins', ['bookmark-block']],
|
||||
['dist/plugins/bookmark-block', ['index.mjs']],
|
||||
['dist/workers', ['plugin.worker.js']],
|
||||
[
|
||||
'node_modules/@toeverything/plugin-infra/dist',
|
||||
['manager.js', 'manager.cjs'],
|
||||
],
|
||||
['node_modules/@blocksuite/global/dist', ['utils.js']],
|
||||
['node_modules/jotai', ['vanilla.js']],
|
||||
] as [entry: string, expected: string[]][];
|
||||
|
||||
await Promise.all(
|
||||
outputList.map(async ([entry, output]) => {
|
||||
const files = await readdir(`${outputRoot}/${entry}`);
|
||||
output.forEach(file => {
|
||||
if (!files.includes(file)) {
|
||||
throw new Error(`File ${entry}/${file} not found`);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
console.log('Output check passed');
|
||||
16
apps/electron/scripts/unzip-macos-arm64.sh
Executable file
16
apps/electron/scripts/unzip-macos-arm64.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set the directory
|
||||
dir="./out/canary/make/zip/darwin/arm64"
|
||||
|
||||
# Get the first file
|
||||
file=$(ls -1 $dir | head -n 1)
|
||||
|
||||
# Check if file exists and is a zip file
|
||||
if [ -f "$dir/$file" ] && [ ${file: -4} == ".zip" ]
|
||||
then
|
||||
# Unzip the file
|
||||
unzip "$dir/$file" -d "zip-out"
|
||||
else
|
||||
echo "No zip file found"
|
||||
fi
|
||||
@@ -1,10 +1,11 @@
|
||||
import path from 'node:path';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { v4 } from 'uuid';
|
||||
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import { removeWithRetry } from '../../../../tests/utils';
|
||||
|
||||
const tmpDir = path.join(__dirname, 'tmp');
|
||||
const appDataPath = path.join(tmpDir, 'app-data');
|
||||
|
||||
@@ -44,11 +45,7 @@ beforeEach(() => {
|
||||
|
||||
afterEach(async () => {
|
||||
existProcess();
|
||||
// wait for the db to be closed on Windows
|
||||
if (process.platform === 'win32') {
|
||||
await setTimeout(200);
|
||||
}
|
||||
await fs.remove(tmpDir);
|
||||
await removeWithRetry(tmpDir);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
|
||||
69
apps/electron/src/helper/db/__tests__/migration.spec.ts
Normal file
69
apps/electron/src/helper/db/__tests__/migration.spec.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import path from 'node:path';
|
||||
|
||||
import { SqliteConnection } from '@affine/native';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { removeWithRetry } from '../../../../tests/utils';
|
||||
import { copyToTemp, migrateToSubdocAndReplaceDatabase } from '../migration';
|
||||
|
||||
const tmpDir = path.join(__dirname, 'tmp');
|
||||
const testDBFilePath = path.resolve(__dirname, 'old-db.affine');
|
||||
|
||||
const appDataPath = path.join(tmpDir, 'app-data');
|
||||
|
||||
vi.mock('../../main-rpc', () => ({
|
||||
mainRPC: {
|
||||
getPath: async () => appDataPath,
|
||||
},
|
||||
}));
|
||||
|
||||
afterEach(async () => {
|
||||
await removeWithRetry(tmpDir);
|
||||
});
|
||||
|
||||
describe('migrateToSubdocAndReplaceDatabase', () => {
|
||||
it('should migrate and replace the database', async () => {
|
||||
const copiedDbFilePath = await copyToTemp(testDBFilePath);
|
||||
await migrateToSubdocAndReplaceDatabase(copiedDbFilePath);
|
||||
|
||||
const db = new SqliteConnection(copiedDbFilePath);
|
||||
await db.connect();
|
||||
|
||||
// check if db has two rows, one for root doc and one for subdoc
|
||||
const rows = await db.getAllUpdates();
|
||||
expect(rows.length).toBe(2);
|
||||
|
||||
const rootUpdate = rows.find(row => row.docId === undefined)!.data;
|
||||
const subdocUpdate = rows.find(row => row.docId !== undefined)!.data;
|
||||
|
||||
expect(rootUpdate).toBeDefined();
|
||||
expect(subdocUpdate).toBeDefined();
|
||||
|
||||
// apply updates
|
||||
const rootDoc = new Y.Doc();
|
||||
Y.applyUpdate(rootDoc, rootUpdate);
|
||||
|
||||
// check if root doc has one subdoc
|
||||
expect(rootDoc.subdocs.size).toBe(1);
|
||||
|
||||
// populates subdoc
|
||||
Y.applyUpdate(rootDoc.subdocs.values().next().value, subdocUpdate);
|
||||
|
||||
// check if root doc's meta is correct
|
||||
const meta = rootDoc.getMap('meta').toJSON();
|
||||
expect(meta.workspaceVersion).toBe(1);
|
||||
expect(meta.name).toBe('hiw');
|
||||
expect(meta.pages.length).toBe(1);
|
||||
const pageMeta = meta.pages[0];
|
||||
expect(pageMeta.title).toBe('Welcome to AFFiNEd');
|
||||
|
||||
// get the subdoc through id
|
||||
const subDoc = rootDoc
|
||||
.getMap('spaces')
|
||||
.get(`space:${pageMeta.id}`) as Y.Doc;
|
||||
expect(subDoc).toEqual(rootDoc.subdocs.values().next().value);
|
||||
|
||||
await db.close();
|
||||
});
|
||||
});
|
||||
BIN
apps/electron/src/helper/db/__tests__/old-db.affine
Normal file
BIN
apps/electron/src/helper/db/__tests__/old-db.affine
Normal file
Binary file not shown.
@@ -5,6 +5,7 @@ import { v4 } from 'uuid';
|
||||
import { afterEach, expect, test, vi } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { removeWithRetry } from '../../../../tests/utils';
|
||||
import { dbSubjects } from '../subjects';
|
||||
|
||||
const tmpDir = path.join(__dirname, 'tmp');
|
||||
@@ -17,7 +18,7 @@ vi.doMock('../../main-rpc', () => ({
|
||||
}));
|
||||
|
||||
afterEach(async () => {
|
||||
await fs.remove(tmpDir);
|
||||
await removeWithRetry(tmpDir);
|
||||
});
|
||||
|
||||
let testYDoc: Y.Doc;
|
||||
|
||||
@@ -119,6 +119,8 @@ export abstract class BaseSQLiteAdapter {
|
||||
`[SQLiteAdapter][${this.role}] addUpdateToSQLite`,
|
||||
'length:',
|
||||
updates.length,
|
||||
'docids',
|
||||
updates.map(u => u.docId),
|
||||
performance.now() - start,
|
||||
'ms'
|
||||
);
|
||||
|
||||
55
apps/electron/src/helper/db/migration.ts
Normal file
55
apps/electron/src/helper/db/migration.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import { migrateToSubdoc } from '@affine/env/blocksuite';
|
||||
import { SqliteConnection } from '@affine/native';
|
||||
import fs from 'fs-extra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { mainRPC } from '../main-rpc';
|
||||
|
||||
export const migrateToSubdocAndReplaceDatabase = async (path: string) => {
|
||||
const db = new SqliteConnection(path);
|
||||
await db.connect();
|
||||
|
||||
const rows = await db.getAllUpdates();
|
||||
const originalDoc = new Y.Doc();
|
||||
|
||||
// 1. apply all updates to the root doc
|
||||
rows.forEach(row => {
|
||||
Y.applyUpdate(originalDoc, row.data);
|
||||
});
|
||||
|
||||
// 2. migrate using migrateToSubdoc
|
||||
const migratedDoc = migrateToSubdoc(originalDoc);
|
||||
|
||||
// 3. replace db rows with the migrated doc
|
||||
await replaceRows(db, migratedDoc, true);
|
||||
|
||||
// 4. close db
|
||||
await db.close();
|
||||
};
|
||||
|
||||
export const copyToTemp = async (path: string) => {
|
||||
const tmpDirPath = resolve(await mainRPC.getPath('sessionData'), 'tmp');
|
||||
const tmpFilePath = resolve(tmpDirPath, nanoid());
|
||||
await fs.ensureDir(tmpDirPath);
|
||||
await fs.copyFile(path, tmpFilePath);
|
||||
return tmpFilePath;
|
||||
};
|
||||
|
||||
async function replaceRows(
|
||||
db: SqliteConnection,
|
||||
doc: Y.Doc,
|
||||
isRoot: boolean
|
||||
): Promise<void> {
|
||||
const migratedUpdates = Y.encodeStateAsUpdate(doc);
|
||||
const docId = isRoot ? undefined : doc.guid;
|
||||
const rows = [{ data: migratedUpdates, docId: docId }];
|
||||
await db.replaceUpdates(docId, rows);
|
||||
await Promise.all(
|
||||
[...doc.subdocs].map(async subdoc => {
|
||||
await replaceRows(db, subdoc, false);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -115,19 +115,43 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
}
|
||||
|
||||
setupListener(docId?: string) {
|
||||
logger.debug(
|
||||
'SecondaryWorkspaceSQLiteDB:setupListener',
|
||||
this.workspaceId,
|
||||
docId
|
||||
);
|
||||
const doc = this.getDoc(docId);
|
||||
if (!doc) {
|
||||
const upstreamDoc = this.upstream.getDoc(docId);
|
||||
if (!doc || !upstreamDoc) {
|
||||
logger.warn(
|
||||
'[SecondaryWorkspaceSQLiteDB] setupListener: doc not found',
|
||||
docId
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const onUpstreamUpdate = (update: Uint8Array, origin: YOrigin) => {
|
||||
if (origin === 'renderer') {
|
||||
logger.debug(
|
||||
'SecondaryWorkspaceSQLiteDB:onUpstreamUpdate',
|
||||
origin,
|
||||
this.workspaceId,
|
||||
docId,
|
||||
update.length
|
||||
);
|
||||
if (origin === 'renderer' || origin === 'self') {
|
||||
// update to upstream yDoc should be replicated to self yDoc
|
||||
this.applyUpdate(update, 'upstream', docId);
|
||||
}
|
||||
};
|
||||
|
||||
const onSelfUpdate = async (update: Uint8Array, origin: YOrigin) => {
|
||||
logger.debug(
|
||||
'SecondaryWorkspaceSQLiteDB:onSelfUpdate',
|
||||
origin,
|
||||
this.workspaceId,
|
||||
docId,
|
||||
update.length
|
||||
);
|
||||
// for self update from upstream, we need to push it to external DB
|
||||
if (origin === 'upstream') {
|
||||
await this.addUpdateToUpdateQueue({
|
||||
@@ -147,15 +171,19 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
});
|
||||
};
|
||||
|
||||
doc.subdocs.forEach(subdoc => {
|
||||
this.setupListener(subdoc.guid);
|
||||
});
|
||||
|
||||
// listen to upstream update
|
||||
this.upstream.yDoc.on('update', onUpstreamUpdate);
|
||||
this.yDoc.on('update', onSelfUpdate);
|
||||
this.yDoc.on('subdocs', onSubdocs);
|
||||
doc.on('update', onSelfUpdate);
|
||||
doc.on('subdocs', onSubdocs);
|
||||
|
||||
this.unsubscribers.add(() => {
|
||||
this.upstream.yDoc.off('update', onUpstreamUpdate);
|
||||
this.yDoc.off('update', onSelfUpdate);
|
||||
this.yDoc.off('subdocs', onSubdocs);
|
||||
doc.off('update', onSelfUpdate);
|
||||
doc.off('subdocs', onSubdocs);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -188,7 +216,10 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
if (doc) {
|
||||
Y.applyUpdate(this.yDoc, data, origin);
|
||||
} else {
|
||||
logger.warn('applyUpdate: doc not found', docId);
|
||||
logger.warn(
|
||||
'[SecondaryWorkspaceSQLiteDB] applyUpdate: doc not found',
|
||||
docId
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
|
||||
update$ = new Subject<void>();
|
||||
|
||||
constructor(public override path: string, public workspaceId: string) {
|
||||
constructor(
|
||||
public override path: string,
|
||||
public workspaceId: string
|
||||
) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
@@ -49,9 +52,21 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
};
|
||||
|
||||
setupListener(docId?: string) {
|
||||
logger.debug(
|
||||
'WorkspaceSQLiteDB:setupListener',
|
||||
this.workspaceId,
|
||||
docId,
|
||||
this.getWorkspaceName()
|
||||
);
|
||||
const doc = this.getDoc(docId);
|
||||
if (doc) {
|
||||
const onUpdate = async (update: Uint8Array, origin: YOrigin) => {
|
||||
logger.debug(
|
||||
'WorkspaceSQLiteDB:onUpdate',
|
||||
this.workspaceId,
|
||||
docId,
|
||||
update.length
|
||||
);
|
||||
const insertRows = [{ data: update, docId }];
|
||||
if (origin === 'renderer') {
|
||||
await this.addUpdateToSQLite(insertRows);
|
||||
@@ -65,7 +80,11 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
logger.debug('external update', this.workspaceId);
|
||||
}
|
||||
};
|
||||
doc.subdocs.forEach(subdoc => {
|
||||
this.setupListener(subdoc.guid);
|
||||
});
|
||||
const onSubdocs = ({ added }: { added: Set<Y.Doc> }) => {
|
||||
logger.info('onSubdocs', this.workspaceId, docId, added);
|
||||
added.forEach(subdoc => {
|
||||
this.setupListener(subdoc.guid);
|
||||
});
|
||||
@@ -132,7 +151,7 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
if (doc) {
|
||||
Y.applyUpdate(doc, data, origin);
|
||||
} else {
|
||||
logger.warn('applyUpdate: doc not found', docId);
|
||||
logger.warn('[WorkspaceSQLiteDB] applyUpdate: doc not found', docId);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import path from 'node:path';
|
||||
|
||||
import { ValidationResult } from '@affine/native';
|
||||
import fs from 'fs-extra';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { ensureSQLiteDB } from '../db/ensure-db';
|
||||
import { copyToTemp, migrateToSubdocAndReplaceDatabase } from '../db/migration';
|
||||
import type { WorkspaceSQLiteDB } from '../db/workspace-db-adapter';
|
||||
import { logger } from '../logger';
|
||||
import { mainRPC } from '../main-rpc';
|
||||
@@ -55,6 +57,7 @@ const ErrorMessages = [
|
||||
'DB_FILE_ALREADY_LOADED',
|
||||
'DB_FILE_PATH_INVALID',
|
||||
'DB_FILE_INVALID',
|
||||
'DB_FILE_MIGRATION_FAILED',
|
||||
'FILE_ALREADY_EXISTS',
|
||||
'UNKNOWN_ERROR',
|
||||
] as const;
|
||||
@@ -191,27 +194,42 @@ export async function loadDBFile(): Promise<LoadDBFileResult> {
|
||||
],
|
||||
message: 'Load Workspace from a AFFiNE file',
|
||||
}));
|
||||
const filePath = ret.filePaths?.[0];
|
||||
if (ret.canceled || !filePath) {
|
||||
let originalPath = ret.filePaths?.[0];
|
||||
if (ret.canceled || !originalPath) {
|
||||
logger.info('loadDBFile canceled');
|
||||
return { canceled: true };
|
||||
}
|
||||
|
||||
// the imported file should not be in app data dir
|
||||
if (filePath.startsWith(await getWorkspacesBasePath())) {
|
||||
if (originalPath.startsWith(await getWorkspacesBasePath())) {
|
||||
logger.warn('loadDBFile: db file in app data dir');
|
||||
return { error: 'DB_FILE_PATH_INVALID' };
|
||||
}
|
||||
|
||||
if (await dbFileAlreadyLoaded(filePath)) {
|
||||
if (await dbFileAlreadyLoaded(originalPath)) {
|
||||
logger.warn('loadDBFile: db file already loaded');
|
||||
return { error: 'DB_FILE_ALREADY_LOADED' };
|
||||
}
|
||||
|
||||
const { SqliteConnection } = await import('@affine/native');
|
||||
|
||||
if (!(await SqliteConnection.validate(filePath))) {
|
||||
// TODO: report invalid db file error?
|
||||
const validationResult = await SqliteConnection.validate(originalPath);
|
||||
|
||||
if (validationResult === ValidationResult.MissingDocIdColumn) {
|
||||
try {
|
||||
const tmpDBPath = await copyToTemp(originalPath);
|
||||
await migrateToSubdocAndReplaceDatabase(tmpDBPath);
|
||||
originalPath = tmpDBPath;
|
||||
} catch (error) {
|
||||
logger.warn(`loadDBFile, migration failed: ${originalPath}`, error);
|
||||
return { error: 'DB_FILE_MIGRATION_FAILED' };
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
validationResult !== ValidationResult.MissingDocIdColumn &&
|
||||
validationResult !== ValidationResult.Valid
|
||||
) {
|
||||
return { error: 'DB_FILE_INVALID' }; // invalid db file
|
||||
}
|
||||
|
||||
@@ -220,14 +238,12 @@ export async function loadDBFile(): Promise<LoadDBFileResult> {
|
||||
const internalFilePath = await getWorkspaceDBPath(workspaceId);
|
||||
|
||||
await fs.ensureDir(await getWorkspacesBasePath());
|
||||
|
||||
await fs.copy(filePath, internalFilePath);
|
||||
logger.info(`loadDBFile, copy: ${filePath} -> ${internalFilePath}`);
|
||||
await fs.copy(originalPath, internalFilePath);
|
||||
logger.info(`loadDBFile, copy: ${originalPath} -> ${internalFilePath}`);
|
||||
|
||||
await storeWorkspaceMeta(workspaceId, {
|
||||
id: workspaceId,
|
||||
mainDBPath: internalFilePath,
|
||||
secondaryDBPath: filePath,
|
||||
});
|
||||
|
||||
return { workspaceId };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { RendererToHelper } from '@toeverything/infra/preload/electron';
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
|
||||
import { events, handlers } from './exposed';
|
||||
@@ -30,7 +31,7 @@ function setupRendererConnection(rendererPort: Electron.MessagePortMain) {
|
||||
});
|
||||
}
|
||||
);
|
||||
const rpc = AsyncCall<PeersAPIs.RendererToHelper>(
|
||||
const rpc = AsyncCall<RendererToHelper>(
|
||||
Object.fromEntries(flattenedHandlers),
|
||||
{
|
||||
channel: {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import type {
|
||||
HelperToMain,
|
||||
MainToHelper,
|
||||
} from '@toeverything/infra/preload/electron';
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
|
||||
import { getExposedMeta } from './exposed';
|
||||
|
||||
const helperToMainServer: PeersAPIs.HelperToMain = {
|
||||
const helperToMainServer: HelperToMain = {
|
||||
getMeta: () => getExposedMeta(),
|
||||
};
|
||||
|
||||
export const mainRPC = AsyncCall<PeersAPIs.MainToHelper>(helperToMainServer, {
|
||||
export const mainRPC = AsyncCall<MainToHelper>(helperToMainServer, {
|
||||
strict: {
|
||||
unknownMessage: false,
|
||||
},
|
||||
|
||||
@@ -4,6 +4,8 @@ import fs from 'fs-extra';
|
||||
import { v4 } from 'uuid';
|
||||
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { removeWithRetry } from '../../../../tests/utils';
|
||||
|
||||
const tmpDir = path.join(__dirname, 'tmp');
|
||||
const appDataPath = path.join(tmpDir, 'app-data');
|
||||
|
||||
@@ -20,7 +22,7 @@ vi.doMock('../../main-rpc', () => ({
|
||||
}));
|
||||
|
||||
afterEach(async () => {
|
||||
await fs.remove(tmpDir);
|
||||
await removeWithRetry(tmpDir);
|
||||
});
|
||||
|
||||
describe('list workspaces', () => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import assert from 'node:assert';
|
||||
import path from 'node:path';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { removeWithRetry } from '../../../tests/utils';
|
||||
import type { MainIPCHandlerMap } from '../exposed';
|
||||
|
||||
const registeredHandlers = new Map<
|
||||
@@ -21,7 +21,7 @@ type WithoutFirstParameter<T> = T extends (_: any, ...args: infer P) => infer R
|
||||
// however this is too hard to be typed correctly
|
||||
async function dispatch<
|
||||
T extends keyof MainIPCHandlerMap,
|
||||
F extends keyof MainIPCHandlerMap[T]
|
||||
F extends keyof MainIPCHandlerMap[T],
|
||||
>(
|
||||
namespace: T,
|
||||
functionName: F,
|
||||
@@ -121,11 +121,7 @@ beforeEach(async () => {
|
||||
afterEach(async () => {
|
||||
// reset registered handlers
|
||||
registeredHandlers.get('before-quit')?.forEach(fn => fn());
|
||||
// wait for the db to be closed on Windows
|
||||
if (process.platform === 'win32') {
|
||||
await setTimeout(200);
|
||||
}
|
||||
await fs.remove(SESSION_DATA_PATH);
|
||||
await removeWithRetry(SESSION_DATA_PATH);
|
||||
});
|
||||
|
||||
describe('UI handlers', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { app, Menu } from 'electron';
|
||||
|
||||
import { revealLogFile } from '../logger';
|
||||
import { checkForUpdatesAndNotify } from '../updater';
|
||||
import { checkForUpdates } from '../updater';
|
||||
import { isMacOS } from '../utils';
|
||||
import { applicationMenuSubjects } from './subject';
|
||||
|
||||
@@ -125,7 +125,7 @@ export function createApplicationMenu() {
|
||||
{
|
||||
label: 'Check for Updates',
|
||||
click: async () => {
|
||||
await checkForUpdatesAndNotify(true);
|
||||
await checkForUpdates(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
9
apps/electron/src/main/clipboard/index.ts
Normal file
9
apps/electron/src/main/clipboard/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { clipboard, type IpcMainInvokeEvent, nativeImage } from 'electron';
|
||||
|
||||
import type { NamespaceHandlers } from '../type';
|
||||
|
||||
export const clipboardHandlers = {
|
||||
copyAsImageFromString: async (_: IpcMainInvokeEvent, dataURL: string) => {
|
||||
clipboard.writeImage(nativeImage.createFromDataURL(dataURL));
|
||||
},
|
||||
} satisfies NamespaceHandlers;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BrowserWindow, dialog, shell } from 'electron';
|
||||
import { BrowserWindow, dialog } from 'electron';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { logger } from '../logger';
|
||||
@@ -39,6 +39,13 @@ export async function savePDFFileAs(
|
||||
|
||||
await BrowserWindow.getFocusedWindow()
|
||||
?.webContents.printToPDF({
|
||||
margins: {
|
||||
marginType: 'custom',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
pageSize: 'A4',
|
||||
printBackground: true,
|
||||
landscape: false,
|
||||
@@ -49,8 +56,6 @@ export async function savePDFFileAs(
|
||||
logger.log(`Wrote PDF successfully to ${filePath}`);
|
||||
});
|
||||
});
|
||||
|
||||
await shell.openPath(filePath);
|
||||
return { filePath };
|
||||
} catch (err) {
|
||||
logger.error('savePDFFileAs', err);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
ClipboardHandlerManager,
|
||||
DebugHandlerManager,
|
||||
ExportHandlerManager,
|
||||
UIHandlerManager,
|
||||
@@ -7,6 +8,7 @@ import type {
|
||||
} from '@toeverything/infra';
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
import { clipboardHandlers } from './clipboard';
|
||||
import { exportHandlers } from './export';
|
||||
import { getLogFilePath, logger, revealLogFile } from './logger';
|
||||
import { uiHandlers } from './ui';
|
||||
@@ -26,6 +28,10 @@ type AllHandlers = {
|
||||
Electron.IpcMainInvokeEvent,
|
||||
DebugHandlerManager
|
||||
>;
|
||||
clipboard: UnwrapManagerHandlerToServerSide<
|
||||
Electron.IpcMainInvokeEvent,
|
||||
ClipboardHandlerManager
|
||||
>;
|
||||
export: UnwrapManagerHandlerToServerSide<
|
||||
Electron.IpcMainInvokeEvent,
|
||||
ExportHandlerManager
|
||||
@@ -44,6 +50,7 @@ type AllHandlers = {
|
||||
export const allHandlers = {
|
||||
debug: debugHandlers,
|
||||
ui: uiHandlers,
|
||||
clipboard: clipboardHandlers,
|
||||
export: exportHandlers,
|
||||
updater: updaterHandlers,
|
||||
} satisfies AllHandlers;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import path from 'node:path';
|
||||
|
||||
import type {
|
||||
HelperToMain,
|
||||
MainToHelper,
|
||||
} from '@toeverything/infra/preload/electron';
|
||||
import { type _AsyncVersionOf, AsyncCall } from 'async-call-rpc';
|
||||
import {
|
||||
app,
|
||||
@@ -36,7 +40,7 @@ class HelperProcessManager {
|
||||
#process: UtilityProcess;
|
||||
|
||||
// a rpc server for the main process -> helper process
|
||||
rpc?: _AsyncVersionOf<PeersAPIs.HelperToMain>;
|
||||
rpc?: _AsyncVersionOf<HelperToMain>;
|
||||
|
||||
static instance = new HelperProcessManager();
|
||||
|
||||
@@ -86,13 +90,13 @@ class HelperProcessManager {
|
||||
]);
|
||||
const appMethods = pickAndBind(app, ['getPath']);
|
||||
|
||||
const mainToHelperServer: PeersAPIs.MainToHelper = {
|
||||
const mainToHelperServer: MainToHelper = {
|
||||
...dialogMethods,
|
||||
...shellMethods,
|
||||
...appMethods,
|
||||
};
|
||||
|
||||
this.rpc = AsyncCall<PeersAPIs.HelperToMain>(mainToHelperServer, {
|
||||
this.rpc = AsyncCall<HelperToMain>(mainToHelperServer, {
|
||||
strict: {
|
||||
// the channel is shared for other purposes as well so that we do not want to
|
||||
// restrict to only JSONRPC messages
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { app } from 'electron';
|
||||
import type { AppUpdater } from 'electron-updater';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { logger } from '../logger';
|
||||
@@ -20,56 +20,55 @@ export const buildType = ReleaseTypeSchema.parse(envBuildType);
|
||||
const mode = process.env.NODE_ENV;
|
||||
const isDev = mode === 'development';
|
||||
|
||||
let _autoUpdater: AppUpdater | null = null;
|
||||
|
||||
export const quitAndInstall = async () => {
|
||||
_autoUpdater?.quitAndInstall();
|
||||
autoUpdater.quitAndInstall();
|
||||
};
|
||||
|
||||
let lastCheckTime = 0;
|
||||
export const checkForUpdatesAndNotify = async (force = true) => {
|
||||
if (!_autoUpdater) {
|
||||
return void 0;
|
||||
}
|
||||
export const checkForUpdates = async (force = true) => {
|
||||
// check every 30 minutes (1800 seconds) at most
|
||||
if (force || lastCheckTime + 1000 * 1800 < Date.now()) {
|
||||
lastCheckTime = Date.now();
|
||||
return await _autoUpdater.checkForUpdatesAndNotify();
|
||||
return await autoUpdater.checkForUpdates();
|
||||
}
|
||||
return void 0;
|
||||
};
|
||||
|
||||
export const registerUpdater = async () => {
|
||||
// so we wrap it in a function
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
|
||||
_autoUpdater = autoUpdater;
|
||||
|
||||
// skip auto update in dev mode
|
||||
if (!_autoUpdater || isDev) {
|
||||
if (isDev) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: support auto update on windows and linux
|
||||
const allowAutoUpdate = isMacOS();
|
||||
|
||||
_autoUpdater.autoDownload = false;
|
||||
_autoUpdater.allowPrerelease = buildType !== 'stable';
|
||||
_autoUpdater.autoInstallOnAppQuit = false;
|
||||
_autoUpdater.autoRunAppAfterInstall = true;
|
||||
_autoUpdater.setFeedURL({
|
||||
autoUpdater.logger = logger;
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.allowPrerelease = buildType !== 'stable';
|
||||
autoUpdater.autoInstallOnAppQuit = false;
|
||||
autoUpdater.autoRunAppAfterInstall = true;
|
||||
|
||||
const feedUrl: Parameters<typeof autoUpdater.setFeedURL>[0] = {
|
||||
channel: buildType,
|
||||
provider: 'github',
|
||||
repo: buildType !== 'internal' ? 'AFFiNE' : 'AFFiNE-Releases',
|
||||
owner: 'toeverything',
|
||||
releaseType: buildType === 'stable' ? 'release' : 'prerelease',
|
||||
});
|
||||
};
|
||||
|
||||
logger.debug('auto-updater feed config', feedUrl);
|
||||
|
||||
autoUpdater.setFeedURL(feedUrl);
|
||||
|
||||
// register events for checkForUpdatesAndNotify
|
||||
_autoUpdater.on('update-available', info => {
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
logger.info('Checking for update');
|
||||
});
|
||||
autoUpdater.on('update-available', info => {
|
||||
logger.info('Update available', info);
|
||||
if (allowAutoUpdate) {
|
||||
_autoUpdater?.downloadUpdate().catch(e => {
|
||||
autoUpdater?.downloadUpdate().catch(e => {
|
||||
logger.error('Failed to download update', e);
|
||||
});
|
||||
logger.info('Update available, downloading...', info);
|
||||
@@ -79,11 +78,14 @@ export const registerUpdater = async () => {
|
||||
allowAutoUpdate,
|
||||
});
|
||||
});
|
||||
_autoUpdater.on('download-progress', e => {
|
||||
autoUpdater.on('update-not-available', info => {
|
||||
logger.info('Update not available', info);
|
||||
});
|
||||
autoUpdater.on('download-progress', e => {
|
||||
logger.info(`Download progress: ${e.percent}`);
|
||||
updaterSubjects.downloadProgress.next(e.percent);
|
||||
});
|
||||
_autoUpdater.on('update-downloaded', e => {
|
||||
autoUpdater.on('update-downloaded', e => {
|
||||
updaterSubjects.updateReady.next({
|
||||
version: e.version,
|
||||
allowAutoUpdate,
|
||||
@@ -92,12 +94,12 @@ export const registerUpdater = async () => {
|
||||
// updaterSubjects.clientDownloadProgress.next(100);
|
||||
logger.info('Update downloaded, ready to install');
|
||||
});
|
||||
_autoUpdater.on('error', e => {
|
||||
autoUpdater.on('error', e => {
|
||||
logger.error('Error while updating client', e);
|
||||
});
|
||||
_autoUpdater.forceDevUpdateConfig = isDev;
|
||||
autoUpdater.forceDevUpdateConfig = isDev;
|
||||
|
||||
app.on('activate', async () => {
|
||||
await checkForUpdatesAndNotify(false);
|
||||
await checkForUpdates(false);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { app } from 'electron';
|
||||
|
||||
import type { NamespaceHandlers } from '../type';
|
||||
import { checkForUpdatesAndNotify, quitAndInstall } from './electron-updater';
|
||||
import { checkForUpdates, quitAndInstall } from './electron-updater';
|
||||
|
||||
export const updaterHandlers = {
|
||||
currentVersion: async () => {
|
||||
@@ -11,7 +11,14 @@ export const updaterHandlers = {
|
||||
return quitAndInstall();
|
||||
},
|
||||
checkForUpdatesAndNotify: async () => {
|
||||
return checkForUpdatesAndNotify(true);
|
||||
const res = await checkForUpdates(true);
|
||||
if (res) {
|
||||
const { updateInfo } = res;
|
||||
return {
|
||||
updateInfo,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
} satisfies NamespaceHandlers;
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
(async () => {
|
||||
const { appInfo, getAffineAPIs } = await import('./affine-apis');
|
||||
const { apis, events } = getAffineAPIs();
|
||||
const { appInfo, getElectronAPIs } = await import(
|
||||
'@toeverything/infra/preload/electron'
|
||||
);
|
||||
const { apis, events } = getElectronAPIs();
|
||||
|
||||
contextBridge.exposeInMainWorld('appInfo', appInfo);
|
||||
contextBridge.exposeInMainWorld('apis', apis);
|
||||
contextBridge.exposeInMainWorld('events', events);
|
||||
contextBridge.exposeInMainWorld('platform', process.platform);
|
||||
|
||||
// Credit to microsoft/vscode
|
||||
const globals = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user