chore: add monorepo tools (#9196)

This commit is contained in:
liuyi
2024-12-24 15:29:48 +08:00
committed by GitHub
parent e3a8b63e38
commit 2443935830
116 changed files with 9390 additions and 1466 deletions

View File

@@ -9,7 +9,7 @@ corepack prepare yarn@stable --activate
yarn install yarn install
# Build Server Dependencies # Build Server Dependencies
yarn workspace @affine/server-native build yarn affine @affine/server-native build
# Create database # Create database
yarn workspace @affine/server prisma db push yarn affine @affine/server prisma db push

View File

@@ -21,6 +21,5 @@
} }
}, },
"updateContentCommand": "bash ./.devcontainer/build.sh", "updateContentCommand": "bash ./.devcontainer/build.sh",
"postCreateCommand": "bash ./.devcontainer/setup-user.sh", "postCreateCommand": "bash ./.devcontainer/setup-user.sh"
"postStartCommand": ["yarn dev", "yarn workspace @affine/server dev"]
} }

View File

@@ -1,7 +0,0 @@
CHANGELOG_URL=
ENABLE_NEW_SETTING_UNSTABLE_API=
ENABLE_CAPTCHA=
CAPTCHA_SITE_KEY=
ENABLE_ENHANCE_SHARE_MODE=
ALLOW_LOCAL_WORKSPACE=
DEBUG_JOTAI=

View File

@@ -3,7 +3,7 @@ description: 'Run Copilot E2E Test'
inputs: inputs:
script: script:
description: 'Script to run' description: 'Script to run'
default: 'yarn workspace @affine-test/affine-cloud-copilot e2e --forbid-only' default: 'yarn affine @affine-test/affine-cloud-copilot e2e --forbid-only'
required: false required: false
openai-key: openai-key:
description: 'OpenAI secret key' description: 'OpenAI secret key'

View File

@@ -18,6 +18,6 @@ runs:
env: env:
NODE_ENV: test NODE_ENV: test
run: | run: |
yarn workspace @affine/server exec prisma generate yarn affine @affine/server prisma generate
yarn workspace @affine/server exec prisma db push yarn affine @affine/server prisma db push
yarn workspace @affine/server data-migration run yarn affine @affine/server data-migration run

View File

@@ -27,7 +27,7 @@ jobs:
electron-install: false electron-install: false
extra-flags: workspaces focus @affine/server extra-flags: workspaces focus @affine/server
- name: Build Server - name: Build Server
run: yarn workspace @affine/server build run: yarn affine @affine/server build
- name: Upload server dist - name: Upload server dist
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -47,7 +47,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- name: Build Core - name: Build Core
run: yarn workspace @affine/web build run: yarn affine @affine/web build
env: env:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
@@ -80,7 +80,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- name: Build Admin - name: Build Admin
run: yarn workspace @affine/admin build run: yarn affine @affine/admin build
env: env:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
@@ -112,7 +112,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- name: Build Mobile - name: Build Mobile
run: yarn workspace @affine/mobile build run: yarn affine @affine/mobile build
env: env:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
@@ -257,7 +257,7 @@ jobs:
yarn workspaces focus @affine/server --production yarn workspaces focus @affine/server --production
- name: Generate Prisma client - name: Generate Prisma client
run: yarn workspace @affine/server prisma generate run: yarn affine @affine/server prisma generate
- name: Setup Version - name: Setup Version
id: version id: version

View File

@@ -89,7 +89,7 @@ jobs:
electron-install: false electron-install: false
full-cache: true full-cache: true
- name: Run i18n codegen - name: Run i18n codegen
run: yarn workspace @affine/i18n build run: yarn affine @affine/i18n build
- name: Run ESLint - name: Run ESLint
run: yarn lint:eslint --max-warnings=0 run: yarn lint:eslint --max-warnings=0
- name: Run Prettier - name: Run Prettier
@@ -162,7 +162,7 @@ jobs:
full-cache: true full-cache: true
- name: Run playwright tests - name: Run playwright tests
run: yarn workspace @affine-test/affine-local e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }} run: yarn affine @affine-test/affine-local e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
- name: Upload test results - name: Upload test results
if: ${{ failure() }} if: ${{ failure() }}
@@ -192,7 +192,7 @@ jobs:
full-cache: true full-cache: true
- name: Run playwright tests - name: Run playwright tests
run: yarn workspace @affine-test/affine-mobile e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }} run: yarn affine @affine-test/affine-mobile e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
- name: Upload test results - name: Upload test results
if: ${{ failure() }} if: ${{ failure() }}
@@ -315,8 +315,7 @@ jobs:
electron-install: false electron-install: false
full-cache: true full-cache: true
- name: Build Electron renderer - name: Build Electron renderer
# always skip cache because its fast, and cache configuration is always changing run: yarn affine @affine/electron bundle
run: yarn build
env: env:
DISTRIBUTION: desktop DISTRIBUTION: desktop
- name: zip web - name: zip web
@@ -377,7 +376,7 @@ jobs:
uses: ./.github/actions/server-test-env uses: ./.github/actions/server-test-env
- name: Run server tests - name: Run server tests
run: yarn workspace @affine/server test:coverage run: yarn affine @affine/server test:coverage
env: env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target' CARGO_TARGET_DIR: '${{ github.workspace }}/target'
COPILOT_OPENAI_API_KEY: 'use_fake_openai_api_key' COPILOT_OPENAI_API_KEY: 'use_fake_openai_api_key'
@@ -480,7 +479,7 @@ jobs:
- name: Run server tests - name: Run server tests
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }} if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
run: yarn workspace @affine/server test:copilot:coverage --forbid-only run: yarn affine @affine/server test:copilot:coverage --forbid-only
env: env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target' CARGO_TARGET_DIR: '${{ github.workspace }}/target'
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }} COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
@@ -570,7 +569,7 @@ jobs:
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }} if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
uses: ./.github/actions/copilot-test uses: ./.github/actions/copilot-test
with: with:
script: yarn workspace @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} script: yarn affine @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
openai-key: ${{ secrets.COPILOT_OPENAI_API_KEY }} openai-key: ${{ secrets.COPILOT_OPENAI_API_KEY }}
fal-key: ${{ secrets.COPILOT_FAL_API_KEY }} fal-key: ${{ secrets.COPILOT_FAL_API_KEY }}
@@ -587,19 +586,19 @@ jobs:
matrix: matrix:
tests: tests:
- name: 'Server E2E Test 1/3' - name: 'Server E2E Test 1/3'
script: yarn workspace @affine-test/affine-cloud e2e --forbid-only --shard=1/3 script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=1/3
- name: 'Server E2E Test 2/3' - name: 'Server E2E Test 2/3'
script: yarn workspace @affine-test/affine-cloud e2e --forbid-only --shard=2/3 script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=2/3
- name: 'Server E2E Test 3/3' - name: 'Server E2E Test 3/3'
script: yarn workspace @affine-test/affine-cloud e2e --forbid-only --shard=3/3 script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=3/3
- name: 'Server Desktop E2E Test' - name: 'Server Desktop E2E Test'
script: | script: |
yarn workspace @affine/electron build:dev yarn affine @affine/electron build:dev
# Workaround for Electron apps failing to initialize on Ubuntu 24.04 due to AppArmor restrictions # Workaround for Electron apps failing to initialize on Ubuntu 24.04 due to AppArmor restrictions
# Disables unprivileged user namespaces restriction to allow Electron apps to run # Disables unprivileged user namespaces restriction to allow Electron apps to run
# Reference: https://github.com/electron/electron/issues/42510 # Reference: https://github.com/electron/electron/issues/42510
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn workspace @affine-test/affine-desktop-cloud e2e xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn affine @affine-test/affine-desktop-cloud e2e
needs: needs:
- build-server-native - build-server-native
- build-native - build-native
@@ -729,7 +728,7 @@ jobs:
- name: Run unit tests - name: Run unit tests
if: ${{ matrix.spec.test }} if: ${{ matrix.spec.test }}
shell: bash shell: bash
run: yarn workspace @affine/electron vitest run: yarn affine @affine/electron vitest
- name: Download web artifact - name: Download web artifact
uses: ./.github/actions/download-web uses: ./.github/actions/download-web
@@ -737,7 +736,7 @@ jobs:
path: packages/frontend/apps/electron/resources/web-static path: packages/frontend/apps/electron/resources/web-static
- name: Build Desktop Layers - name: Build Desktop Layers
run: yarn workspace @affine/electron build run: yarn affine @affine/electron build
- name: Run desktop tests - name: Run desktop tests
if: ${{ matrix.spec.os == 'ubuntu-latest' }} if: ${{ matrix.spec.os == 'ubuntu-latest' }}
@@ -746,11 +745,11 @@ jobs:
# Disables unprivileged user namespaces restriction to allow Electron apps to run # Disables unprivileged user namespaces restriction to allow Electron apps to run
# Reference: https://github.com/electron/electron/issues/42510 # Reference: https://github.com/electron/electron/issues/42510
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn workspace @affine-test/affine-desktop e2e xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn affine @affine-test/affine-desktop e2e
- name: Run desktop tests - name: Run desktop tests
if: ${{ matrix.spec.test && matrix.spec.os != 'ubuntu-latest' }} if: ${{ matrix.spec.test && matrix.spec.os != 'ubuntu-latest' }}
run: yarn workspace @affine-test/affine-desktop e2e run: yarn affine @affine-test/affine-desktop e2e
- name: Make bundle (macOS) - name: Make bundle (macOS)
if: ${{ matrix.spec.target == 'aarch64-apple-darwin' }} if: ${{ matrix.spec.target == 'aarch64-apple-darwin' }}
@@ -758,7 +757,7 @@ jobs:
SKIP_BUNDLE: true SKIP_BUNDLE: true
SKIP_WEB_BUILD: true SKIP_WEB_BUILD: true
HOIST_NODE_MODULES: 1 HOIST_NODE_MODULES: 1
run: yarn workspace @affine/electron package --platform=darwin --arch=arm64 run: yarn affine @affine/electron package --platform=darwin --arch=arm64
- name: Make Bundle (Linux) - name: Make Bundle (Linux)
run: | run: |
@@ -768,7 +767,7 @@ jobs:
flatpak update flatpak update
# some flatpak deps need git protocol.file.allow # some flatpak deps need git protocol.file.allow
git config --global protocol.file.allow always git config --global protocol.file.allow always
yarn workspace @affine/electron make --platform=linux --arch=x64 yarn affine @affine/electron make --platform=linux --arch=x64
if: ${{ matrix.spec.target == 'x86_64-unknown-linux-gnu' }} if: ${{ matrix.spec.target == 'x86_64-unknown-linux-gnu' }}
env: env:
SKIP_WEB_BUILD: 1 SKIP_WEB_BUILD: 1
@@ -777,7 +776,7 @@ jobs:
- name: Output check - name: Output check
if: ${{ matrix.spec.os == 'macos-14' && matrix.spec.arch == 'arm64' }} if: ${{ matrix.spec.os == 'macos-14' && matrix.spec.arch == 'arm64' }}
run: | run: |
yarn workspace @affine/electron exec node --loader ts-node/esm/transpile-only ./scripts/macos-arm64-output-check.ts yarn affine @affine/electron node ./scripts/macos-arm64-output-check.ts
- name: Upload test results - name: Upload test results
if: ${{ failure() }} if: ${{ failure() }}

View File

@@ -82,7 +82,7 @@ jobs:
uses: ./.github/actions/server-test-env uses: ./.github/actions/server-test-env
- name: Run server tests - name: Run server tests
run: yarn workspace @affine/server test:copilot:coverage --forbid-only run: yarn affine @affine/server test:copilot:coverage --forbid-only
env: env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target' CARGO_TARGET_DIR: '${{ github.workspace }}/target'
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }} COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
@@ -147,7 +147,7 @@ jobs:
- name: Run Copilot E2E Test ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - name: Run Copilot E2E Test ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
uses: ./.github/actions/copilot-test uses: ./.github/actions/copilot-test
with: with:
script: yarn workspace @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} script: yarn affine @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
openai-key: ${{ secrets.COPILOT_OPENAI_API_KEY }} openai-key: ${{ secrets.COPILOT_OPENAI_API_KEY }}
fal-key: ${{ secrets.COPILOT_FAL_API_KEY }} fal-key: ${{ secrets.COPILOT_FAL_API_KEY }}

View File

@@ -52,7 +52,7 @@ jobs:
- name: Setup @sentry/cli - name: Setup @sentry/cli
uses: ./.github/actions/setup-sentry uses: ./.github/actions/setup-sentry
- name: generate-assets - name: generate-assets
run: yarn workspace @affine/electron generate-assets run: yarn affine @affine/electron generate-assets
env: env:
SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine' SENTRY_PROJECT: 'affine'
@@ -122,7 +122,7 @@ jobs:
path: packages/frontend/apps/electron/resources/web-static path: packages/frontend/apps/electron/resources/web-static
- name: Build Desktop Layers - name: Build Desktop Layers
run: yarn workspace @affine/electron build run: yarn affine @affine/electron build
- name: Signing By Apple Developer ID - name: Signing By Apple Developer ID
if: ${{ matrix.spec.platform == 'darwin' }} if: ${{ matrix.spec.platform == 'darwin' }}
@@ -142,7 +142,7 @@ jobs:
git config --global protocol.file.allow always git config --global protocol.file.allow always
- name: make - name: make
run: yarn workspace @affine/electron make --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }} run: yarn affine @affine/electron make --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
env: env:
SKIP_WEB_BUILD: 1 SKIP_WEB_BUILD: 1
HOIST_NODE_MODULES: 1 HOIST_NODE_MODULES: 1
@@ -236,10 +236,10 @@ jobs:
path: packages/frontend/apps/electron/resources/web-static path: packages/frontend/apps/electron/resources/web-static
- name: Build Desktop Layers - name: Build Desktop Layers
run: yarn workspace @affine/electron build run: yarn affine @affine/electron build
- name: package - name: package
run: yarn workspace @affine/electron package --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }} run: yarn affine @affine/electron package --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
env: env:
SKIP_WEB_BUILD: 1 SKIP_WEB_BUILD: 1
HOIST_NODE_MODULES: 1 HOIST_NODE_MODULES: 1
@@ -314,10 +314,10 @@ jobs:
run: Expand-Archive -Path signed.zip -DestinationPath packages/frontend/apps/electron/out run: Expand-Archive -Path signed.zip -DestinationPath packages/frontend/apps/electron/out
- name: Make squirrel.windows installer - name: Make squirrel.windows installer
run: yarn workspace @affine/electron make-squirrel --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }} run: yarn affine @affine/electron make-squirrel --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
- name: Make nsis.windows installer - name: Make nsis.windows installer
run: yarn workspace @affine/electron make-nsis --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }} run: yarn affine @affine/electron make-nsis --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
- name: Zip artifacts for faster upload - name: Zip artifacts for faster upload
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make/* -DestinationPath archive.zip run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make/* -DestinationPath archive.zip

View File

@@ -68,7 +68,7 @@ jobs:
- name: Setup @sentry/cli - name: Setup @sentry/cli
uses: ./.github/actions/setup-sentry uses: ./.github/actions/setup-sentry
- name: Build Mobile - name: Build Mobile
run: yarn workspace @affine/ios build run: yarn affine @affine/ios build
env: env:
PUBLIC_PATH: '/' PUBLIC_PATH: '/'
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }} MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
@@ -101,7 +101,7 @@ jobs:
- name: Setup @sentry/cli - name: Setup @sentry/cli
uses: ./.github/actions/setup-sentry uses: ./.github/actions/setup-sentry
- name: Build Mobile - name: Build Mobile
run: yarn workspace @affine/android build run: yarn affine @affine/android build
env: env:
PUBLIC_PATH: '/' PUBLIC_PATH: '/'
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }} MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
@@ -216,7 +216,7 @@ jobs:
- name: Auto increment version code - name: Auto increment version code
id: bump id: bump
if: ${{ env.BUILD_TARGET == 'distribution' }} if: ${{ env.BUILD_TARGET == 'distribution' }}
run: yarn workspace @affine/playstore-auto-bump bump run: yarn affine @affine/playstore-auto-bump bump
env: env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.auth.outputs.credentials_file_path }} GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.auth.outputs.credentials_file_path }}
- name: Build - name: Build

4
.gitignore vendored
View File

@@ -80,3 +80,7 @@ apps/web/next-routes.conf
packages/frontend/templates/edgeless packages/frontend/templates/edgeless
packages/frontend/core/public/static/templates packages/frontend/core/public/static/templates
# script
af
af.cmd

View File

@@ -1,30 +1,35 @@
yarn.lock # we will make this file shared by prettier|eslint|oxlint
target **/node_modules
lib
test-results
.next
out
dist
.yarn .yarn
.github/helm .github
_next .vscode
storybook-static
web-static
public
packages/backend/server/src/schema.gql
packages/backend/server/src/base/error/errors.gen.ts
packages/frontend/i18n/src/i18n-generated.ts
packages/frontend/i18n/src/i18n-completenesses.json
packages/frontend/graphql/src/graphql/index.ts
.yarnrc.yml .yarnrc.yml
packages/frontend/templates/*.gen.ts
packages/frontend/templates/onboarding
# auto-generated by NAPI-RS # compiled output
# fixme(@joooye34): need script to check and generate ignore list here .coverage
.nx/**
target
test-results
**/dist
**/lib
**/storybook-static
**/web-static
**/public
**/e2e-dist-*
**/static
# generated files
**/*.gen.ts
**/*.gql
**/*.d.ts
# per files
tools/cli/src/webpack/error-handler.js
packages/backend/native/index.d.ts packages/backend/native/index.d.ts
packages/frontend/native/index.d.ts packages/frontend/native/index.d.ts
packages/frontend/native/index.js packages/frontend/native/index.js
compose.yaml packages/frontend/graphql/src/graphql/index.ts
packages/frontend/graphql/src/schema.ts
packages/frontend/apps/android/App/app/build/**
blocksuite/tests-legacy/snapshots blocksuite/tests-legacy/snapshots
**/.storybook

View File

@@ -1,25 +1,21 @@
{ {
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "Run Dev",
"type": "node-terminal",
"request": "launch",
"command": "yarn run dev"
},
{
"name": "Run Dev Locally",
"type": "node-terminal",
"request": "launch",
"command": "yarn run dev:local"
},
{ {
"name": "Launch AFFiNE Cloud", "name": "Launch AFFiNE Cloud",
"type": "node", "type": "node",
"request": "launch", "request": "launch",
"runtimeExecutable": "yarn", "runtimeExecutable": "yarn",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"runtimeArgs": ["workspace", "@affine/server", "dev"] "runtimeArgs": ["affine", "@affine/server", "dev"]
},
{
"name": "Lanuch AFFiNE Web",
"type": "node",
"request": "launch",
"runtimeExecutable": "yarn",
"cwd": "${workspaceFolder}",
"runtimeArgs": ["affine", "@affine/web", "dev"]
} }
] ]
} }

View File

@@ -87,13 +87,13 @@ This could take a while if you build it for the first time.
Note: use `strip` from system instead of `binutils` if you are running MacOS. [see problem here](https://github.com/toeverything/AFFiNE/discussions/2840) Note: use `strip` from system instead of `binutils` if you are running MacOS. [see problem here](https://github.com/toeverything/AFFiNE/discussions/2840)
``` ```
yarn workspace @affine/native build yarn affine @affine/native build
``` ```
### Build Server Dependencies ### Build Server Dependencies
```sh ```sh
yarn workspace @affine/server-native build yarn affine @affine/server-native build
``` ```
## Testing ## Testing

View File

@@ -46,7 +46,7 @@ Please refer to `Build Native Dependencies` section in [BUILDING.md](./BUILDING.
On Mac & Linux On Mac & Linux
```shell ```shell
BUILD_TYPE=canary yarn workspace @affine/electron generate-assets BUILD_TYPE=canary yarn affine @affine/electron generate-assets
``` ```
On Windows (powershell) On Windows (powershell)
@@ -90,7 +90,7 @@ yarn install
Note: you need to comment out `osxSign` and `osxNotarize` in `forge.config.js` to skip signing and notarizing the app. Note: you need to comment out `osxSign` and `osxNotarize` in `forge.config.js` to skip signing and notarizing the app.
```shell ```shell
BUILD_TYPE=canary SKIP_WEB_BUILD=1 HOIST_NODE_MODULES=1 yarn workspace @affine/electron make BUILD_TYPE=canary SKIP_WEB_BUILD=1 HOIST_NODE_MODULES=1 yarn affine @affine/electron make
``` ```
#### Windows #### Windows
@@ -101,9 +101,9 @@ Making the windows installer is a bit different. Right now we provide two instal
$env:BUILD_TYPE="canary" $env:BUILD_TYPE="canary"
$env:SKIP_WEB_BUILD=1 $env:SKIP_WEB_BUILD=1
$env:HOIST_NODE_MODULES=1 $env:HOIST_NODE_MODULES=1
yarn workspace @affine/electron package yarn affine @affine/electron package
yarn workspace @affine/electron make-squirrel yarn affine @affine/electron make-squirrel
yarn workspace @affine/electron make-nsis yarn affine @affine/electron make-nsis
``` ```
Once the build is complete, you can find the paths to the binaries in the terminal output. Once the build is complete, you can find the paths to the binaries in the terminal output.

View File

@@ -18,7 +18,7 @@ docker compose -f ./.docker/dev/compose.yml up -d
```sh ```sh
# build native # build native
yarn workspace @affine/server-native build yarn affine @affine/server-native build
``` ```
## Prepare dev environment ## Prepare dev environment

View File

@@ -1,3 +1,5 @@
import { readFileSync } from 'node:fs';
import eslint from '@eslint/js'; import eslint from '@eslint/js';
import rxjs from '@smarttools/eslint-plugin-rxjs'; import rxjs from '@smarttools/eslint-plugin-rxjs';
import tsParser from '@typescript-eslint/parser'; import tsParser from '@typescript-eslint/parser';
@@ -10,33 +12,13 @@ import sonarjs from 'eslint-plugin-sonarjs';
import unicorn from 'eslint-plugin-unicorn'; import unicorn from 'eslint-plugin-unicorn';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
const ignoreList = readFileSync('.prettierignore', 'utf-8')
.split('\n')
.filter(line => line.trim() && !line.startsWith('#'));
export default tseslint.config( export default tseslint.config(
{ {
ignores: [ ignores: ignoreList,
'**/node_modules',
'**/dist',
'**/.next',
'**/out',
'**/storybook-static',
'**/affine-out',
'**/_next',
'**/lib',
'**/.eslintrc.js',
'**/e2e-dist-*',
'**/static',
'**/web-static',
'**/public',
'**/.coverage',
'.nx/**',
'.yarn/**',
'**/*.d.ts',
'.github/**/*',
'packages/frontend/component/.storybook/**/*',
'packages/frontend/i18n/src/i18n-generated.ts',
'packages/frontend/i18n/src/i18n-completenesses.json',
'packages/frontend/templates/*.gen.ts',
'packages/frontend/apps/android/App/app/build/**',
],
}, },
{ {
settings: { settings: {
@@ -218,7 +200,7 @@ export default tseslint.config(
{ {
files: [ files: [
'packages/**/*.{ts,tsx}', 'packages/**/*.{ts,tsx}',
'tools/cli/**/*.{ts,tsx}', 'tools/**/*.{ts,tsx}',
'blocksuite/**/*.{ts,tsx}', 'blocksuite/**/*.{ts,tsx}',
], ],
rules: { rules: {

View File

@@ -5,13 +5,41 @@
"correctness": "error", "correctness": "error",
"perf": "error" "perf": "error"
}, },
"ignorePatterns": ["tools/cli/src/webpack/error-handler.js"], "ignorePatterns": [
"**/node_modules",
".yarn",
".github",
".vscode",
".yarnrc.yml",
".coverage",
".nx/**",
"target",
"test-results",
"**/dist",
"**/lib",
"**/storybook-static",
"**/web-static",
"**/public",
"**/e2e-dist-*",
"**/static",
"**/*.gen.ts",
"**/*.gql",
"**/*.d.ts",
"tools/cli/src/webpack/error-handler.js",
"packages/backend/native/index.d.ts",
"packages/frontend/native/index.d.ts",
"packages/frontend/native/index.js",
"packages/frontend/graphql/src/graphql/index.ts",
"packages/frontend/graphql/src/schema.ts",
"packages/frontend/apps/android/App/app/build/**",
"blocksuite/tests-legacy/snapshots",
"**/.storybook"
],
"rules": { "rules": {
"import/named": "allow", "import/named": "allow",
"no-await-in-loop": "allow", "no-await-in-loop": "allow",
"promise/no-callback-in-promise": "allow", "promise/no-callback-in-promise": "allow",
"typescript/ban-types": "allow", "typescript/ban-types": "allow",
"array-callback-return": "error", "array-callback-return": "error",
"constructor-super": "error", "constructor-super": "error",
"eqeqeq": ["error", "smart"], "eqeqeq": ["error", "smart"],
@@ -172,7 +200,8 @@
"files": [ "files": [
"*.{spec,test,e2e,stories}.{ts,tsx}", "*.{spec,test,e2e,stories}.{ts,tsx}",
"tests/**/*.ts", "tests/**/*.ts",
"packages/backend/server/tests/**/*.ts" "packages/backend/server/tests/**/*.ts",
"tools/**.*"
], ],
"rules": { "rules": {
"typescript/no-non-null-assertion": "off", "typescript/no-non-null-assertion": "off",

View File

@@ -18,12 +18,10 @@
"node": "<21.0.0" "node": "<21.0.0"
}, },
"scripts": { "scripts": {
"dev": "yarn workspace @affine/cli dev", "affine": "yarn workspace @affine-tools/cli affine",
"build": "yarn workspace @affine/cli bundle", "af": "yarn workspace @affine-tools/cli affine",
"dev:electron": "yarn workspace @affine/electron dev", "dev": "yarn affine dev",
"build:electron": "yarn workspace @affine/electron build", "build": "yarn affine build",
"build:server-native": "yarn workspace @affine/server-native build",
"start:web-static": "yarn workspace @affine/web static-server",
"lint:eslint": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" eslint --report-unused-disable-directives-severity=off . --cache", "lint:eslint": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" eslint --report-unused-disable-directives-severity=off . --cache",
"lint:eslint:fix": "yarn lint:eslint --fix --fix-type problem,suggestion,layout", "lint:eslint:fix": "yarn lint:eslint --fix --fix-type problem,suggestion,layout",
"lint:prettier": "prettier --ignore-unknown --cache --check .", "lint:prettier": "prettier --ignore-unknown --cache --check .",
@@ -34,9 +32,8 @@
"test": "vitest --run", "test": "vitest --run",
"test:ui": "vitest --ui", "test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage", "test:coverage": "vitest run --coverage",
"typecheck": "tsc -b tsconfig.json", "typecheck": "tsc -b tsconfig.json --verbose",
"postinstall": "node ./scripts/check-version.mjs && yarn workspace @affine/i18n i18n-codegen gen && yarn husky install", "postinstall": "yarn affine init && yarn husky"
"prepare": "husky"
}, },
"lint-staged": { "lint-staged": {
"*": "prettier --write --ignore-unknown --cache", "*": "prettier --write --ignore-unknown --cache",
@@ -52,7 +49,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@affine/cli": "workspace:*", "@affine-tools/cli": "workspace:*",
"@capacitor/cli": "^6.2.0", "@capacitor/cli": "^6.2.0",
"@eslint/js": "^9.16.0", "@eslint/js": "^9.16.0",
"@faker-js/faker": "^9.3.0", "@faker-js/faker": "^9.3.0",

View File

@@ -19,4 +19,3 @@ export declare function mergeUpdatesInApplyWay(updates: Array<Buffer>): Buffer
export declare function mintChallengeResponse(resource: string, bits?: number | undefined | null): Promise<string> export declare function mintChallengeResponse(resource: string, bits?: number | undefined | null): Promise<string>
export declare function verifyChallengeResponse(response: string, bits: number, resource: string): Promise<boolean> export declare function verifyChallengeResponse(response: string, bits: number, resource: string): Promise<boolean>

View File

@@ -11,7 +11,7 @@ yarn
### Build Native binding ### Build Native binding
```bash ```bash
yarn workspace @affine/server-native build yarn affine @affine/server-native build
``` ```
### Run server ### Run server

View File

@@ -9,15 +9,14 @@
}, },
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "node --loader ts-node/esm/transpile-only.mjs ./src/index.ts",
"dev": "nodemon ./src/index.ts", "dev": "nodemon ./src/index.ts",
"test": "ava --concurrency 1 --serial", "test": "ava --concurrency 1 --serial",
"test:copilot": "ava \"tests/**/copilot-*.spec.ts\"", "test:copilot": "ava \"tests/**/copilot-*.spec.ts\"",
"test:coverage": "c8 ava --concurrency 1 --serial", "test:coverage": "c8 ava --concurrency 1 --serial",
"test:copilot:coverage": "c8 ava --timeout=5m \"tests/**/copilot-*.spec.ts\"", "test:copilot:coverage": "c8 ava --timeout=5m \"tests/**/copilot-*.spec.ts\"",
"postinstall": "prisma generate", "data-migration": "NODE_ENV=script node ./src/data/index.ts",
"data-migration": "NODE_ENV=script node --loader ts-node/esm/transpile-only.mjs ./src/data/index.ts", "predeploy": "yarn prisma migrate deploy && node --import ./scripts/register.js ./dist/data/index.js run",
"predeploy": "yarn prisma migrate deploy && node --import ./scripts/register.js ./dist/data/index.js run" "postinstall": "prisma generate"
}, },
"dependencies": { "dependencies": {
"@apollo/server": "^4.11.2", "@apollo/server": "^4.11.2",
@@ -119,10 +118,7 @@
}, },
"workerThreads": false, "workerThreads": false,
"nodeArguments": [ "nodeArguments": [
"--trace-sigint", "--trace-sigint"
"--loader",
"ts-node/esm/transpile-only.mjs",
"--es-module-specifier-resolution=node"
], ],
"watchMode": { "watchMode": {
"ignoreChanges": [ "ignoreChanges": [
@@ -139,7 +135,6 @@
"./src/prelude.ts" "./src/prelude.ts"
], ],
"environmentVariables": { "environmentVariables": {
"TS_NODE_PROJECT": "./tests/tsconfig.json",
"NODE_ENV": "test", "NODE_ENV": "test",
"MAILER_HOST": "0.0.0.0", "MAILER_HOST": "0.0.0.0",
"MAILER_PORT": "1025", "MAILER_PORT": "1025",
@@ -152,12 +147,6 @@
}, },
"nodemonConfig": { "nodemonConfig": {
"exec": "node", "exec": "node",
"script": "./src/index.ts",
"nodeArgs": [
"--loader",
"ts-node/esm.mjs",
"--es-module-specifier-resolution=node"
],
"ignore": [ "ignore": [
"**/__tests__/**", "**/__tests__/**",
"**/dist/**", "**/dist/**",
@@ -166,8 +155,6 @@
"env": { "env": {
"NODE_ENV": "development", "NODE_ENV": "development",
"AFFINE_SERVER_EXTERNAL_URL": "http://localhost:8080", "AFFINE_SERVER_EXTERNAL_URL": "http://localhost:8080",
"TS_NODE_TRANSPILE_ONLY": true,
"TS_NODE_PROJECT": "./tsconfig.json",
"DEBUG": "affine:*", "DEBUG": "affine:*",
"FORCE_COLOR": true, "FORCE_COLOR": true,
"DEBUG_COLORS": true "DEBUG_COLORS": true

View File

@@ -15,9 +15,6 @@
}, },
{ {
"path": "../../../blocksuite/affine/all" "path": "../../../blocksuite/affine/all"
},
{
"path": "./tsconfig.node.json"
} }
] ]
} }

View File

@@ -1,12 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true,
"outDir": "lib",
"noEmit": false
},
"include": ["vite.config.ts"]
}

View File

@@ -62,7 +62,8 @@
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"
}, },
"scripts": { "scripts": {
"build": "cross-env DISTRIBUTION=admin yarn workspace @affine/cli bundle", "build": "affine bundle",
"dev": "affine bundle --dev",
"update-shadcn": "shadcn-ui add -p src/components/ui" "update-shadcn": "shadcn-ui add -p src/components/ui"
}, },
"exports": { "exports": {

View File

@@ -1,7 +1,8 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: ['class'], darkMode: ['class'],
content: ['./src/**/*.{ts,tsx}'], // TODO(@forehalo): we are not running webpack in admin dir
content: ['./packages/frontend/admin/src/**/*.{ts,tsx}'],
prefix: '', prefix: '',
theme: { theme: {
container: { container: {

View File

@@ -5,6 +5,6 @@ AFFiNE Android app.
## Build ## Build
- yarn install - yarn install
- BUILD_TYPE=canary PUBLIC_PATH="/" yarn workspace @affine/android build - BUILD_TYPE=canary PUBLIC_PATH="/" yarn affine @affine/android build
- yarn workspace @affine/android cap sync - yarn affine @affine/android cap sync
- yarn workspace @affine/android cap open android - yarn affine @affine/android cap open android

View File

@@ -5,9 +5,8 @@
"private": true, "private": true,
"browser": "src/index.tsx", "browser": "src/index.tsx",
"scripts": { "scripts": {
"build": "cross-env DISTRIBUTION=android yarn workspace @affine/cli bundle", "build": "affine bundle",
"dev": "yarn workspace @affine/cli dev", "dev": "affine bundle --dev"
"static-server": "cross-env DISTRIBUTION=android yarn workspace @affine/cli dev --static"
}, },
"dependencies": { "dependencies": {
"@affine/component": "workspace:*", "@affine/component": "workspace:*",

View File

@@ -7,7 +7,7 @@ To run AFFiNE Desktop Client Application locally, run the following commands:
```sh ```sh
# in repo root # in repo root
yarn install yarn install
yarn workspace @affine/native build yarn affine @affine/native build
yarn dev yarn dev
# in packages/frontend/apps/electron # in packages/frontend/apps/electron

View File

@@ -2,6 +2,7 @@
"name": "@affine/electron", "name": "@affine/electron",
"private": true, "private": true,
"version": "0.18.0", "version": "0.18.0",
"main": "./dist/main.js",
"author": "toeverything", "author": "toeverything",
"repository": { "repository": {
"url": "https://github.com/toeverything/AFFiNE", "url": "https://github.com/toeverything/AFFiNE",
@@ -11,19 +12,20 @@
"homepage": "https://github.com/toeverything/AFFiNE", "homepage": "https://github.com/toeverything/AFFiNE",
"scripts": { "scripts": {
"start": "electron .", "start": "electron .",
"dev": "DEV_SERVER_URL=http://localhost:8080 node --loader ts-node/esm/transpile-only ./scripts/dev.ts", "dev": "cross-env DEV_SERVER_URL=http://localhost:8080 node ./scripts/dev.ts",
"dev:prod": "yarn node --loader ts-node/esm/transpile-only scripts/dev.ts", "dev:prod": "node ./scripts/dev.ts",
"build": "NODE_ENV=production node --loader ts-node/esm/transpile-only scripts/build-layers.ts", "build": "cross-env NODE_ENV=production node ./scripts/build-layers.ts",
"build:dev": "NODE_ENV=development node --loader ts-node/esm/transpile-only scripts/build-layers.ts", "build:dev": "node ./scripts/build-layers.ts",
"generate-assets": "node --loader ts-node/esm/transpile-only scripts/generate-assets.ts", "bundle": "affine bundle",
"package": "cross-env NODE_OPTIONS=\"--loader ts-node/esm/transpile-only\" electron-forge package", "generate-assets": "node ./scripts/generate-assets.ts",
"make": "cross-env NODE_OPTIONS=\"--loader ts-node/esm/transpile-only\" electron-forge make", "package": "electron-forge package",
"make-squirrel": "node --loader ts-node/esm/transpile-only scripts/make-squirrel.ts", "make": "electron-forge make",
"make-nsis": "node --loader ts-node/esm/transpile-only scripts/make-nsis.ts" "make-squirrel": "node ./scripts/make-squirrel.ts",
"make-nsis": "node ./scripts/make-nsis.ts"
}, },
"main": "./dist/main.js",
"devDependencies": { "devDependencies": {
"@affine-test/kit": "workspace:*", "@affine-test/kit": "workspace:*",
"@affine-tools/utils": "workspace:*",
"@affine/component": "workspace:*", "@affine/component": "workspace:*",
"@affine/core": "workspace:*", "@affine/core": "workspace:*",
"@affine/electron-api": "workspace:*", "@affine/electron-api": "workspace:*",

View File

@@ -1,8 +1,8 @@
import { resolve } from 'node:path'; import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports import { getBuildConfig } from '@affine-tools/utils/build-config';
import { getBuildConfig } from '@affine/cli/src/webpack/runtime-config'; import { Package } from '@affine-tools/utils/workspace';
import { sentryEsbuildPlugin } from '@sentry/esbuild-plugin'; import { sentryEsbuildPlugin } from '@sentry/esbuild-plugin';
import type { BuildOptions, Plugin } from 'esbuild'; import type { BuildOptions, Plugin } from 'esbuild';
@@ -24,12 +24,10 @@ export const config = (): BuildOptions => {
'process.env.NODE_ENV': process.env.NODE_ENV, 'process.env.NODE_ENV': process.env.NODE_ENV,
REPLACE_ME_BUILD_ENV: process.env.BUILD_TYPE ?? 'stable', REPLACE_ME_BUILD_ENV: process.env.BUILD_TYPE ?? 'stable',
...Object.entries( ...Object.entries(
getBuildConfig({ getBuildConfig(new Package('@affine/electron'), {
channel: (process.env.BUILD_TYPE as any) ?? 'canary',
distribution: 'desktop',
mode: mode:
process.env.NODE_ENV === 'production' ? 'production' : 'development', process.env.NODE_ENV === 'production' ? 'production' : 'development',
static: false, channel: (process.env.BUILD_TYPE as any) ?? 'canary',
}) })
).reduce( ).reduce(
(def, [key, val]) => { (def, [key, val]) => {

View File

@@ -34,6 +34,7 @@ function spawnOrReloadElectron() {
const ext = process.platform === 'win32' ? '.cmd' : ''; const ext = process.platform === 'win32' ? '.cmd' : '';
const exe = resolve(rootDir, 'node_modules', '.bin', `electron${ext}`); const exe = resolve(rootDir, 'node_modules', '.bin', `electron${ext}`);
delete process.env['NODE_OPTIONS'];
spawnProcess = spawn(exe, ['.'], { spawnProcess = spawn(exe, ['.'], {
cwd: electronDir, cwd: electronDir,
env: process.env, env: process.env,

View File

@@ -33,6 +33,9 @@
}, },
{ {
"path": "../../../../tests/kit" "path": "../../../../tests/kit"
},
{
"path": "../../../../tools/utils"
} }
], ],
"ts-node": { "ts-node": {

View File

@@ -5,7 +5,7 @@
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "ESNext",
"resolveJsonModule": true, "resolveJsonModule": true,
"moduleResolution": "Node", "moduleResolution": "bundler",
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"noEmit": false, "noEmit": false,
"outDir": "./lib/scripts", "outDir": "./lib/scripts",
@@ -17,5 +17,10 @@
"ts-node": { "ts-node": {
"esm": true, "esm": true,
"experimentalSpecifierResolution": "node" "experimentalSpecifierResolution": "node"
},
"references": [
{
"path": "../../../../tools/utils"
} }
]
} }

View File

@@ -1,3 +1,4 @@
// TODO(@forehalo): reuse '@affine-tools/utils' once it's ready to switch to esm module
import { resolve } from 'node:path'; import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';

View File

@@ -0,0 +1,12 @@
// TODO(@forehalo): all packages would become 'module'
const path = require('node:path');
module.exports.config = {
entry: {
app: './renderer/index.tsx',
shell: './renderer/shell/index.tsx',
},
output: {
path: path.resolve(__dirname, './renderer/dist'),
},
};

View File

@@ -5,9 +5,9 @@ AFFiNE iOS app.
## Build ## Build
- `yarn install` - `yarn install`
- `BUILD_TYPE=canary PUBLIC_PATH="/" yarn workspace @affine/ios build` - `BUILD_TYPE=canary PUBLIC_PATH="/" yarn affine @affine/ios build`
- `yarn workspace @affine/ios cap sync` - `yarn affine @affine/ios cap sync`
- `yarn workspace @affine/ios cap open ios` - `yarn affine @affine/ios cap open ios`
## Live Reload ## Live Reload
@@ -16,5 +16,5 @@ AFFiNE iOS app.
- `yarn install` - `yarn install`
- `yarn dev` - `yarn dev`
- select `ios` for the "Distribution" option - select `ios` for the "Distribution" option
- `yarn workspace @affine/ios sync:dev` - `yarn affine @affine/ios sync:dev`
- `yarn workspace @affine/ios cap open ios` - `yarn affine @affine/ios cap open ios`

View File

@@ -5,11 +5,10 @@
"private": true, "private": true,
"browser": "src/index.tsx", "browser": "src/index.tsx",
"scripts": { "scripts": {
"build": "cross-env DISTRIBUTION=ios yarn workspace @affine/cli bundle", "build": "affine bundle",
"dev": "yarn workspace @affine/cli dev", "dev": "affine bundle --dev",
"sync": "yarn cap sync", "sync": "yarn cap sync",
"sync:dev": "CAP_SERVER_URL=http://localhost:8080 yarn cap sync", "sync:dev": "CAP_SERVER_URL=http://localhost:8080 yarn cap sync"
"static-server": "cross-env DISTRIBUTION=ios yarn workspace @affine/cli dev --static"
}, },
"dependencies": { "dependencies": {
"@affine/component": "workspace:*", "@affine/component": "workspace:*",

View File

@@ -5,9 +5,8 @@
"private": true, "private": true,
"browser": "src/index.tsx", "browser": "src/index.tsx",
"scripts": { "scripts": {
"build": "cross-env DISTRIBUTION=mobile yarn workspace @affine/cli bundle", "build": "affine bundle",
"dev": "yarn workspace @affine/cli dev", "dev": "affine bundle --dev"
"static-server": "cross-env DISTRIBUTION=mobile yarn workspace @affine/cli dev --static"
}, },
"dependencies": { "dependencies": {
"@affine/component": "workspace:*", "@affine/component": "workspace:*",

View File

@@ -5,9 +5,8 @@
"private": true, "private": true,
"browser": "src/index.tsx", "browser": "src/index.tsx",
"scripts": { "scripts": {
"build": "cross-env DISTRIBUTION=web yarn workspace @affine/cli bundle", "build": "affine bundle",
"dev": "yarn workspace @affine/cli dev", "dev": "affine bundle --dev"
"static-server": "yarn workspace @affine/cli dev --static"
}, },
"dependencies": { "dependencies": {
"@affine/component": "workspace:*", "@affine/component": "workspace:*",

View File

@@ -3,7 +3,7 @@ import { StorybookConfig } from '@storybook/react-vite';
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
import swc from 'unplugin-swc'; import swc from 'unplugin-swc';
import { mergeConfig } from 'vite'; import { mergeConfig } from 'vite';
import { getBuildConfig } from '@affine/cli/src/webpack/runtime-config'; import { getBuildConfig } from '@affine-tools/utils/build-config';
export default { export default {
stories: ['../src/ui/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'], stories: ['../src/ui/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'],

View File

@@ -20,7 +20,6 @@
"react-dom": "^19.0.0" "react-dom": "^19.0.0"
}, },
"dependencies": { "dependencies": {
"@affine/cli": "workspace:*",
"@affine/debug": "workspace:*", "@affine/debug": "workspace:*",
"@affine/electron-api": "workspace:*", "@affine/electron-api": "workspace:*",
"@affine/graphql": "workspace:*", "@affine/graphql": "workspace:*",

View File

@@ -1,7 +1,12 @@
{ {
"extends": "../../../tsconfig.json", "extends": "../../../tsconfig.json",
"exclude": ["lib"], "exclude": ["lib"],
"include": ["./src/**/*", "./src/**/*.json", "./src/type.d.ts"], "include": [
"./src/**/*",
"./src/**/*.json",
"./src/type.d.ts",
"./.storybook"
],
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"noEmit": false, "noEmit": false,

View File

@@ -2,6 +2,7 @@
User-agent: * User-agent: *
Allow: / Allow: /
Allow: /api/workspaces/*/blobs/*
Disallow: /oauth/* Disallow: /oauth/*
Disallow: /workspace/* Disallow: /workspace/*
Disallow: /public-workspace/* Disallow: /public-workspace/*

View File

@@ -28,12 +28,6 @@
}, },
{ {
"path": "../../../blocksuite/affine/all" "path": "../../../blocksuite/affine/all"
},
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.server.json"
} }
] ]
} }

View File

@@ -1,23 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"composite": true,
"target": "ESNext",
"module": "ESNext",
"verbatimModuleSyntax": false,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"outDir": "./lib/.webpack",
"rootDir": "."
},
"include": [".webpack/*.ts"],
"references": [
{
"path": "../../../tools/cli"
},
{
"path": "../../common/env"
}
]
}

View File

@@ -1,13 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"composite": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"outDir": "./lib/server"
},
"include": ["server.mts"]
}

View File

@@ -30,7 +30,5 @@ generates:
plugins: plugins:
- typescript - typescript
- typescript-operations - typescript-operations
- add:
content: '/* eslint-disable */'
- ./export-gql-plugin.cjs: - ./export-gql-plugin.cjs:
output: ./src/graphql/index.ts output: ./src/graphql/index.ts

View File

@@ -21,7 +21,7 @@
"vitest": "2.1.8" "vitest": "2.1.8"
}, },
"scripts": { "scripts": {
"postinstall": "gql-gen --errors-only" "build": "gql-gen --errors-only"
}, },
"dependencies": { "dependencies": {
"@affine/env": "workspace:*", "@affine/env": "workspace:*",

View File

@@ -1,4 +1,4 @@
/* oxlint-disable */
export type Maybe<T> = T | null; export type Maybe<T> = T | null;
export type InputMaybe<T> = T | null; export type InputMaybe<T> = T | null;
export type Exact<T extends { [key: string]: unknown }> = { export type Exact<T extends { [key: string]: unknown }> = {

View File

@@ -4,7 +4,7 @@
"list": [ "list": [
{ {
"input": "./src/resources/en.json", "input": "./src/resources/en.json",
"output": "./src/i18n-generated", "output": "./src/i18n.gen",
"parser": { "parser": {
"type": "i18next", "type": "i18next",
"contextSeparator": "$", "contextSeparator": "$",

View File

@@ -39,7 +39,7 @@ function calcCompletenesses() {
writeFileSync( writeFileSync(
join(pkgRoot, 'src', 'i18n-completenesses.json'), join(pkgRoot, 'src', 'i18n-completenesses.json'),
JSON.stringify(completenesses, null, 2) JSON.stringify(completenesses, null, 2) + '\n'
); );
} }

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ import type { BackendModule, i18n } from 'i18next';
import i18next from 'i18next'; import i18next from 'i18next';
import { initReactI18next } from 'react-i18next'; import { initReactI18next } from 'react-i18next';
import type { useAFFiNEI18N } from './i18n-generated'; import type { useAFFiNEI18N } from './i18n.gen';
import type { Language } from './resources'; import type { Language } from './resources';
import { SUPPORTED_LANGUAGES } from './resources'; import { SUPPORTED_LANGUAGES } from './resources';

View File

@@ -4,7 +4,8 @@
"sideEffect": false, "sideEffect": false,
"version": "0.18.0", "version": "0.18.0",
"scripts": { "scripts": {
"postinstall": "node ./build-edgeless.mjs && node ./build-stickers.mjs" "build": "node ./build-edgeless.mjs && node ./build-stickers.mjs",
"postinstall": "yarn build"
}, },
"type": "module", "type": "module",
"exports": { "exports": {

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env node
import('@affine/bump-blocksuite');

View File

@@ -1,3 +0,0 @@
#!/bin/bash
cargo update -p jwst-codec -p jwst-core -p jwst-storage

View File

@@ -1,15 +0,0 @@
const semver = await import('semver').catch(
() => import('../packages/backend/server/node_modules/semver/index.js')
);
import packageJson from '../package.json' with { type: 'json' };
const { engines } = packageJson;
const version = engines.node;
if (!semver.satisfies(process.version, version)) {
console.log(
`Required node version ${version} not satisfied with current version ${process.version}.`
);
process.exit(1);
}

View File

@@ -1,105 +0,0 @@
const { exec } = await import('node:child_process');
const { fileURLToPath } = await import('node:url');
const { readdir } = await import('node:fs/promises');
const { join } = await import('node:path');
async function readJsonFromCommit(commit, file) {
function readFile(commit, file) {
return new Promise((resolve, reject) => {
exec(`git show ${commit}:${file}`, (error, stdout, stderr) => {
if (error) {
reject(stderr);
} else {
resolve(stdout);
}
});
});
}
try {
const content = await readFile(commit, file);
return JSON.parse(content);
} catch {}
}
function checkBlocksuiteChanged(oldPkg, newPkg) {
const changed = new Set();
const keys = ['dependencies', 'devDependencies'];
keys.forEach(key => {
const oldDeps = oldPkg[key] || {};
const newDeps = newPkg[key] || {};
Object.keys(newDeps).forEach(dep => {
if (newDeps[dep] !== oldDeps[dep] && dep.startsWith('@blocksuite/')) {
changed.add(dep);
}
});
});
return changed;
}
async function findPackageJson(root) {
const packages = new Set();
async function walk(dir) {
const files = await readdir(dir, { withFileTypes: true });
for (const file of files) {
if (file.isDirectory() && file.name !== 'node_modules') {
await walk(join(dir, file.name));
} else if (file.name === 'package.json') {
let path = join(dir.replace(root, ''), file.name);
if (path.startsWith('/')) {
path = path.slice(1);
}
packages.add(path);
}
}
}
await walk(root);
return packages;
}
async function main() {
const commitHash = process.argv[2] || process.env.GITHUB_BASE_REF;
const currentHead = process.argv[3] || 'HEAD';
if (!commitHash) {
console.error('Missing base ref commit hash, skipping check.');
process.exit(1);
}
const changedPackages = new Set();
const folders = await findPackageJson(
join(fileURLToPath(import.meta.url), '..', '..')
);
for (const packagePath of folders) {
const old = await readJsonFromCommit(commitHash, packagePath);
const current = await readJsonFromCommit(currentHead, packagePath);
console.log('checking:', packagePath);
if (
old &&
current &&
typeof old === 'object' &&
typeof current === 'object'
) {
for (const p of checkBlocksuiteChanged(old, current)) {
changedPackages.add(p);
}
}
}
if (changedPackages.size > 0) {
console.log('Blocksuite packages have been updated.', changedPackages);
process.exit(0);
} else {
console.log('No changes to Blocksuite packages.');
process.exit(1);
}
}
main().catch(console.error);

View File

@@ -1,11 +1,10 @@
import { getBuildConfig } from '@affine/cli/src/webpack/runtime-config';
import { setupGlobal } from '@affine/env/global'; import { setupGlobal } from '@affine/env/global';
import { getBuildConfig } from '@affine-tools/utils/build-config';
import { Package } from '@affine-tools/utils/workspace';
globalThis.BUILD_CONFIG = getBuildConfig({ globalThis.BUILD_CONFIG = getBuildConfig(new Package('@affine/web'), {
distribution: 'web',
mode: 'development', mode: 'development',
channel: 'canary', channel: 'canary',
static: false,
}); });
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {

View File

@@ -26,9 +26,8 @@ const config: PlaywrightTestConfig = {
retries: 3, retries: 3,
reporter: process.env.CI ? 'github' : 'list', reporter: process.env.CI ? 'github' : 'list',
webServer: [ webServer: [
// Intentionally not building the web, reminds you to run it by yourself.
{ {
command: 'yarn -T run start:web-static', command: 'yarn run -T affine dev -p @affine/web',
port: 8080, port: 8080,
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -37,7 +36,7 @@ const config: PlaywrightTestConfig = {
}, },
}, },
{ {
command: 'yarn workspace @affine/server start', command: 'yarn run -T affine dev -p @affine/server',
port: 3010, port: 3010,
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,

View File

@@ -26,9 +26,12 @@ const config: PlaywrightTestConfig = {
retries: process.env.COPILOT ? 1 : 3, retries: process.env.COPILOT ? 1 : 3,
reporter: process.env.CI ? 'github' : 'list', reporter: process.env.CI ? 'github' : 'list',
webServer: [ webServer: [
// Intentionally not building the web, reminds you to run it by yourself.
{ {
command: 'yarn -T run start:web-static', // TODO(@forehalo):
// in ci, all the target will be built,
// we could download the builds from archives
// and then run the web with simple http serve, it's will be faster
command: 'yarn run -T affine dev -p @affine/web',
port: 8080, port: 8080,
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -37,12 +40,10 @@ const config: PlaywrightTestConfig = {
}, },
}, },
{ {
command: 'yarn workspace @affine/server start', command: 'yarn run -T affine dev -p @affine/server',
port: 3010, port: 3010,
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
stdout: 'pipe',
stderr: 'pipe',
env: { env: {
DATABASE_URL: DATABASE_URL:
process.env.DATABASE_URL ?? process.env.DATABASE_URL ??

View File

@@ -23,7 +23,7 @@ const config: PlaywrightTestConfig = {
webServer: [ webServer: [
// Intentionally not building the web, reminds you to run it by yourself. // Intentionally not building the web, reminds you to run it by yourself.
{ {
command: 'yarn -T run start:web-static', command: 'yarn run -T affine bundle -p @affine/electron --dev',
port: 8080, port: 8080,
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -34,7 +34,7 @@ const config: PlaywrightTestConfig = {
}, },
}, },
{ {
command: 'yarn workspace @affine/server start', command: 'yarn run -T affine dev -p @affine/server',
port: 3010, port: 3010,
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,

View File

@@ -41,7 +41,7 @@ if (process.env.DEV_SERVER_URL) {
); );
config.webServer = [ config.webServer = [
{ {
command: 'yarn run start:web-static', command: 'yarn run -T affine bundle -p @affine/electron --dev',
port: 8080, port: 8080,
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,

View File

@@ -46,7 +46,7 @@ const config: PlaywrightTestConfig = {
webServer: [ webServer: [
// Intentionally not building the web, reminds you to run it by yourself. // Intentionally not building the web, reminds you to run it by yourself.
{ {
command: 'yarn run start:web-static', command: 'yarn run -T affine dev -p @affine/web',
port: 8080, port: 8080,
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,

View File

@@ -49,7 +49,7 @@ const config: PlaywrightTestConfig = {
webServer: [ webServer: [
// Intentionally not building the web, reminds you to run it by yourself. // Intentionally not building the web, reminds you to run it by yourself.
{ {
command: 'yarn workspace @affine/mobile static-server', command: 'yarn run -T affine dev -p @affine/mobile',
port: 8080, port: 8080,
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,

View File

@@ -53,16 +53,22 @@ const cloudUserSchema = z.object({
export const runPrisma = async <T>( export const runPrisma = async <T>(
cb: ( cb: (
prisma: InstanceType< prisma: InstanceType<
// eslint-disable-next-line @typescript-eslint/consistent-type-imports // oxlint-disable-next-line @typescript-eslint/consistent-type-imports
typeof import('../../../packages/backend/server/node_modules/@prisma/client').PrismaClient typeof import('../../../packages/backend/server/node_modules/@prisma/client').PrismaClient
> >
) => Promise<T> ) => Promise<T>
): Promise<T> => { ): Promise<T> => {
const { const {
PrismaClient, PrismaClient,
// eslint-disable-next-line @typescript-eslint/no-var-requires // oxlint-disable-next-line @typescript-eslint/consistent-type-imports
} = require('../../../packages/backend/server/node_modules/@prisma/client'); } = await import(
const client = new PrismaClient(); '../../../packages/backend/server/node_modules/@prisma/client'
);
const client = new PrismaClient({
datasourceUrl:
process.env.DATABASE_URL ||
'postgresql://affine:affine@localhost:5432/affine',
});
await client.$connect(); await client.$connect();
try { try {
return await cb(client); return await cb(client);

View File

@@ -1,13 +0,0 @@
# Bump BlockSuite
To update BlockSuite, run following command in project root:
```sh
node scripts/bump-blocksuite.js
```
For network issue, try setting proxy environment variables:
```sh
export http_proxy=http://127.0.0.1:7890
```

View File

@@ -1,176 +0,0 @@
import { execSync } from 'node:child_process';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Clipboard } from '@napi-rs/clipboard';
import {
FetchOptions,
ProxyOptions,
RemoteCallbacks,
Repository,
Sort,
} from '@napi-rs/simple-git';
import chalk from 'chalk';
import { ProxyAgent, setGlobalDispatcher } from 'undici';
import corePackage from '../../packages/frontend/core/package.json' with { type: 'json' };
const clipboard = new Clipboard();
const oldHash = corePackage.dependencies['@blocksuite/affine'].split('-').pop();
const info = await fetch('https://registry.npmjs.org/@blocksuite/affine').then(
res => res.json()
);
const latestVersion = info['dist-tags'].latest;
const latestHash = latestVersion.split('-').pop();
const latestGitHead = info.versions[latestHash].gitHead;
const oldGitHead = info.versions[oldHash].gitHead;
if (oldHash === latestHash) {
console.info(chalk.greenBright('Already updated'));
process.exit(0);
}
if (process.env.http_proxy) {
setGlobalDispatcher(new ProxyAgent(process.env.http_proxy));
}
console.info(`Upgrade blocksuite from ${oldHash} -> ${latestHash}`);
const blockSuiteDeps = execSync(`yarn info -A --name-only --json`, {
encoding: 'utf8',
});
const blocksuiteDepsList = blockSuiteDeps
.split('\n')
.map(s => s.trim())
.filter(Boolean)
.map(s => s.substring(1, s.length - 1))
.filter(
s => s.startsWith('@blocksuite') && !s.startsWith('@blocksuite/icons')
)
.map(s => s.split('@npm').at(0));
for (const pkg of blocksuiteDepsList) {
const command = `yarn up ${pkg}@${latestVersion}`;
console.info(chalk.bgCyan(`Executing ${command}`));
execSync(command, {
stdio: 'inherit',
});
}
console.info(`Upgrade complete`);
const repo = new Repository(
join(fileURLToPath(import.meta.url), '..', '..', '..', '..', 'BlockSuite')
);
const remote = repo.remoteAnonymous(
'https://github.com/toeverything/BlockSuite.git'
);
remote.fetch(
['master'],
new FetchOptions().proxyOptions(new ProxyOptions().auto()).remoteCallback(
new RemoteCallbacks().transferProgress(progress => {
if (progress.totalDeltas && progress.totalObjects) {
console.log(
`${(
(progress.receivedObjects / progress.totalObjects) * 50 +
(progress.indexedDeltas / progress.totalDeltas) * 50
).toFixed(2)}%`
);
}
})
)
);
const commits = {
Features: [],
Bugfix: [],
Refactor: [],
Misc: [],
};
if (!latestGitHead) {
console.info('latestGitHead is not found');
console.info('Skip generating changelog');
process.exit(0);
}
for (const oid of repo
.revWalk()
.push(latestGitHead)
.setSorting(Sort.Time & Sort.Topological)) {
if (oid.startsWith(oldGitHead)) {
break;
}
const commit = repo.findCommit(oid);
const summary = commit.summary();
if (summary.startsWith('feat')) {
commits.Features.push(commit);
} else if (summary.startsWith('fix')) {
commits.Bugfix.push(commit);
} else if (summary.startsWith('refactor')) {
commits.Refactor.push(commit);
} else {
commits.Misc.push(commit);
}
}
clipboard.setText(await formatCommits(commits));
console.info(`Changelog copied to clipboard`);
async function formatCommits(commits) {
return `## Features
${await Promise.all(commits.Features.map(format)).then(commits =>
commits.join('\n')
)}
## Bugfix
${await Promise.all(commits.Bugfix.map(format)).then(commits =>
commits.join('\n')
)}
## Refactor
${await Promise.all(commits.Refactor.map(format)).then(commits =>
commits.join('\n')
)}
## Misc
${await Promise.all(commits.Misc.map(format)).then(commits =>
commits.join('\n')
)}
`;
/**
* @param {import('./index').Commit} commit
* @returns string
*/
async function format(commit) {
const summary = commit.summary();
const match = summary.match(/\(#(\d+)\)/);
if (match) {
const [_, pull] = match;
const pullInfo = await fetch(
`https://api.github.com/repos/toeverything/BlockSuite/pulls/${pull}`,
{
headers: {
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
'X-GitHub-Api-Version': '2022-11-28',
},
}
)
.then(res => res.json())
.catch(() => ({ user: {} }));
const {
user: { login },
} = pullInfo;
return `- https://github.com/toeverything/BlockSuite/pull/${pull} @${login}`;
}
return `- ${summary}`;
}
}

View File

@@ -1,14 +0,0 @@
{
"name": "@affine/bump-blocksuite",
"version": "0.18.0",
"type": "module",
"main": "index.js",
"private": true,
"description": "Generate changelog from blocksuite version change",
"dependencies": {
"@napi-rs/clipboard": "^1.1.2",
"@napi-rs/simple-git": "^0.1.19",
"chalk": "^5.3.0",
"undici": "^7.1.0"
}
}

113
tools/cli/README.md Normal file
View File

@@ -0,0 +1,113 @@
# AFFiNE Monorepo Cli
## Start
```bash
yarn affine -h
```
### Run build command defined in package.json
```bash
yarn affine i18n build
# or
yarn build -p i18n
```
### Run dev command defined in package.json
```bash
yarn affine web dev
# or
yarn dev -p i18n
```
### Clean
```bash
yarn affine clean --dist --rust
# clean node_modules
yarn affine clean --node-modules
```
### Init
> Generate files that make the monorepo work properly, the per project codegen will not be included anymore
```bash
yarn affine init
```
## Tricks
### Define scripts to run a .ts files without `--loader ts-node/esm/transpile-only`
`affine run` will automatically inject `ts-node`'s transpile service(swc used) for your scripts
```json
{
"name": "@affine/demo"
"scripts": {
"dev": "node ./dev.ts"
}
}
```
```bash
affine @affine/demo dev
```
### Short your key presses
```bash
# af is also available for running the scripts
yarn af web build
```
#### by custom shell script
> personally, I use 'af'
create file `af` in the root of AFFiNE project with the following content
```bash
#!/usr/bin/env sh
./tools/scripts/bin/runner.js affine.ts $@
```
or on windows:
```cmd
node "./tools/cli/bin/runner.js" affine.ts %*
```
and give it executable permission
```bash
chmod a+x ./af
# now you can run scripts with simply
./af web build
```
if you want to go further, but for vscode(or other forks) only, add the following to your `.vscode/settings.json`
```json
{
"terminal.integrated.env.osx": {
"PATH": "${env:PATH}:${cwd}"
},
"terminal.integrated.env.linux": {
"PATH": "${env:PATH}:${cwd}"
},
"terminal.integrated.env.windows": {
"PATH": "${env:PATH};${cwd}"
}
}
```
restart all the integrated terminals and now you get:
```bash
af web build
```

68
tools/cli/bin/runner.js Executable file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env node
import { spawn } from 'node:child_process';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const scriptsFolder = join(fileURLToPath(import.meta.url), '..', '..');
const scriptsSrcFolder = join(scriptsFolder, 'src');
const projectRoot = join(scriptsFolder, '..', '..');
const loader = join(scriptsFolder, 'register.js');
const [node, _self, file, ...options] = process.argv;
if (!file) {
console.error(`Please provide a file to run, e.g. 'run src/index.{js/ts}'`);
process.exit(1);
}
const fileLocationCandidates = new Set([
process.cwd(),
scriptsSrcFolder,
projectRoot,
]);
const lookups = [];
/**
* @type {string | undefined}
*/
let scriptLocation;
for (const location of fileLocationCandidates) {
const fileCandidates = [file, `${file}.js`, `${file}.ts`];
for (const candidate of fileCandidates) {
const candidateLocation = join(location, candidate);
if (existsSync(candidateLocation)) {
scriptLocation = candidateLocation;
break;
}
lookups.push(candidateLocation);
}
}
if (!scriptLocation) {
console.error(
`File ${file} not found, please make sure the first parameter passed to 'run' script is a valid js or ts file.`
);
console.error(`Searched locations: `);
lookups.forEach(location => {
console.error(` - ${location}`);
});
process.exit(1);
}
const nodeOptions = [];
if (
scriptLocation.endsWith('.ts') ||
scriptLocation.startsWith(scriptsFolder)
) {
nodeOptions.unshift(`--import=${pathToFileURL(loader)}`);
} else {
nodeOptions.unshift('--experimental-specifier-resolution=node');
}
spawn(node, [...nodeOptions, scriptLocation, ...options], {
stdio: 'inherit',
}).on('exit', code => {
process.exit(code);
});

14
tools/cli/hooks.js Normal file
View File

@@ -0,0 +1,14 @@
import { create, createEsmHooks, register } from 'ts-node';
const service = create({
experimentalSpecifierResolution: 'node',
esm: true,
transpileOnly: true,
swc: true,
});
register(service);
const hooks = createEsmHooks(service);
export const resolve = hooks.resolve;
export const load = hooks.load;

View File

@@ -1,33 +1,39 @@
{ {
"name": "@affine/cli", "name": "@affine-tools/cli",
"version": "0.0.1",
"type": "module", "type": "module",
"private": true, "private": true,
"devDependencies": { "bin": {
"@affine/env": "workspace:*", "r": "./bin/runner.js"
"@affine/templates": "workspace:*", },
"exports": {
"./loader": "./loader.js"
},
"scripts": {
"affine": "r ./src/affine.ts"
},
"dependencies": {
"@affine-tools/utils": "workspace:*",
"@aws-sdk/client-s3": "^3.709.0", "@aws-sdk/client-s3": "^3.709.0",
"@blocksuite/affine": "workspace:*",
"@clack/core": "^0.4.0", "@clack/core": "^0.4.0",
"@clack/prompts": "^0.9.0", "@clack/prompts": "^0.9.0",
"@magic-works/i18n-codegen": "^0.6.1",
"@napi-rs/simple-git": "^0.1.19", "@napi-rs/simple-git": "^0.1.19",
"@perfsee/webpack": "^1.13.0", "@perfsee/webpack": "^1.13.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@sentry/webpack-plugin": "^2.22.7", "@sentry/webpack-plugin": "^2.22.7",
"@types/mime-types": "^2.1.4",
"@types/webpack-env": "^1.18.5",
"@vanilla-extract/webpack-plugin": "^2.3.15", "@vanilla-extract/webpack-plugin": "^2.3.15",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"clipanion": "^3.2.1",
"copy-webpack-plugin": "^12.0.2", "copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"cssnano": "^7.0.6", "cssnano": "^7.0.6",
"dotenv": "^16.4.7",
"html-webpack-plugin": "^5.6.3", "html-webpack-plugin": "^5.6.3",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"mini-css-extract-plugin": "^2.9.2", "mini-css-extract-plugin": "^2.9.2",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
"prettier": "^3.4.2",
"react-refresh": "^0.16.0", "react-refresh": "^0.16.0",
"source-map-loader": "^5.0.0", "source-map-loader": "^5.0.0",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
@@ -35,14 +41,16 @@
"tailwindcss": "^3.4.16", "tailwindcss": "^3.4.16",
"terser-webpack-plugin": "^5.3.10", "terser-webpack-plugin": "^5.3.10",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"vite": "^6.0.3", "typanion": "^3.14.0",
"typescript": "^5.5.4",
"webpack": "^5.97.1", "webpack": "^5.97.1",
"webpack-dev-server": "^5.2.0", "webpack-dev-server": "^5.2.0",
"webpack-merge": "^6.0.1" "webpack-merge": "^6.0.1"
}, },
"scripts": { "devDependencies": {
"bundle": "node --loader ts-node/esm/transpile-only.mjs ./src/bin/build.ts", "@types/lodash-es": "^4.17.12",
"dev": "node --loader ts-node/esm/transpile-only.mjs ./src/bin/dev.ts" "@types/mime-types": "^2.1.4",
}, "@types/node": "^20.17.10",
"version": "0.18.0" "@types/webpack-env": "^1.18.5"
}
} }

3
tools/cli/register.js Normal file
View File

@@ -0,0 +1,3 @@
import { register } from 'node:module';
register('./hooks.js', import.meta.url);

32
tools/cli/src/affine.ts Normal file
View File

@@ -0,0 +1,32 @@
import { Workspace } from '@affine-tools/utils/workspace';
import { Cli } from 'clipanion';
import { BuildCommand } from './build';
import { BundleCommand } from './bundle';
import { CleanCommand } from './clean';
import { CodegenCommand } from './codegen';
import type { CliContext } from './context';
import { DevCommand } from './dev';
import { RunCommand } from './run';
const cli = new Cli<CliContext>({
binaryName: 'affine',
binaryVersion: '0.0.0',
binaryLabel: 'AFFiNE Monorepo Tools',
enableColors: true,
enableCapture: true,
});
cli.register(RunCommand);
cli.register(CodegenCommand);
cli.register(CleanCommand);
cli.register(BuildCommand);
cli.register(DevCommand);
cli.register(BundleCommand);
await cli.runExit(process.argv.slice(2), {
workspace: new Workspace(),
stdin: process.stdin,
stdout: process.stdout,
stderr: process.stderr,
});

View File

@@ -1,66 +0,0 @@
import { spawn } from 'node:child_process';
import webpack from 'webpack';
import { getCwdFromDistribution } from '../config/cwd.cjs';
import type { BuildFlags } from '../config/index.js';
import { createWebpackConfig } from '../webpack/webpack.config.js';
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const buildType = process.env.BUILD_TYPE_OVERRIDE || process.env.BUILD_TYPE;
if (process.env.BUILD_TYPE_OVERRIDE) {
process.env.BUILD_TYPE = process.env.BUILD_TYPE_OVERRIDE;
}
const getChannel = () => {
switch (buildType) {
case 'canary':
case 'beta':
case 'stable':
case 'internal':
return buildType;
case '':
throw new Error('BUILD_TYPE is not set');
default: {
throw new Error(
`BUILD_TYPE must be one of canary, beta, stable, internal, received [${buildType}]`
);
}
}
};
let entry: BuildFlags['entry'];
const { DISTRIBUTION = 'web' } = process.env;
const cwd = getCwdFromDistribution(DISTRIBUTION);
if (DISTRIBUTION === 'desktop') {
entry = { app: './index.tsx', shell: './shell/index.tsx' };
}
const flags = {
distribution: DISTRIBUTION as BuildFlags['distribution'],
mode: 'production',
channel: getChannel(),
coverage: process.env.COVERAGE === 'true',
entry,
static: false,
} satisfies BuildFlags;
spawn('yarn', ['workspace', '@affine/i18n', 'build'], {
stdio: 'inherit',
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
webpack(createWebpackConfig(cwd!, flags), (err, stats) => {
if (err) {
console.error(err);
process.exit(1);
}
if (stats?.hasErrors()) {
console.error(stats.toString('errors-only'));
process.exit(1);
}
});

View File

@@ -1,146 +0,0 @@
import { spawn } from 'node:child_process';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import * as p from '@clack/prompts';
import { config } from 'dotenv';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import { getCwdFromDistribution, projectRoot } from '../config/cwd.cjs';
import type { BuildFlags } from '../config/index.js';
import { createWebpackConfig } from '../webpack/webpack.config.js';
const flags: BuildFlags = {
distribution:
(process.env.DISTRIBUTION as BuildFlags['distribution']) ?? 'web',
mode: 'development',
static: false,
channel: 'canary',
coverage: process.env.COVERAGE === 'true',
};
const files = ['.env', '.env.local'];
for (const file of files) {
if (existsSync(join(projectRoot, file))) {
config({
path: join(projectRoot, file),
});
console.log(`${file} loaded`);
break;
}
}
const buildFlags = process.argv.includes('--static')
? { ...flags, static: true }
: ((await p.group(
{
distribution: () =>
p.select({
message: 'Distribution',
options: [
{
value: 'web',
},
{
value: 'desktop',
},
{
value: 'admin',
},
{
value: 'mobile',
},
{
value: 'ios',
},
],
initialValue: 'web',
}),
mode: () =>
p.select({
message: 'Mode',
options: [
{
value: 'development',
},
{
value: 'production',
},
],
initialValue: 'development',
}),
channel: () =>
p.select({
message: 'Channel',
options: [
{
value: 'canary',
},
{
value: 'beta',
},
{
value: 'stable',
},
],
initialValue: 'canary',
}),
coverage: () =>
p.confirm({
message: 'Enable coverage',
initialValue: process.env.COVERAGE === 'true',
}),
},
{
onCancel: () => {
p.cancel('Operation cancelled.');
process.exit(0);
},
}
)) as BuildFlags);
flags.distribution = buildFlags.distribution;
flags.mode = buildFlags.mode;
flags.channel = buildFlags.channel;
flags.coverage = buildFlags.coverage;
flags.static = buildFlags.static;
flags.entry = undefined;
const cwd = getCwdFromDistribution(flags.distribution);
process.env.DISTRIBUTION = flags.distribution;
if (flags.distribution === 'desktop') {
flags.entry = {
app: join(cwd, 'index.tsx'),
shell: join(cwd, 'shell/index.tsx'),
};
}
console.info(flags);
if (!flags.static) {
spawn('yarn', ['workspace', '@affine/i18n', 'dev'], {
stdio: 'inherit',
shell: true,
});
}
try {
// @ts-expect-error no types
await import('@affine/templates/build-edgeless');
const config = createWebpackConfig(cwd, flags);
if (flags.static) {
config.watch = false;
}
const compiler = webpack(config);
// Start webpack
const devServer = new WebpackDevServer(config.devServer, compiler);
await devServer.start();
} catch (error) {
console.error('Error during build:', error);
process.exit(1);
}

15
tools/cli/src/build.ts Normal file
View File

@@ -0,0 +1,15 @@
import { PackageCommand } from './command';
export class BuildCommand extends PackageCommand {
static override paths = [['build'], ['b']];
async execute() {
const args = ['affine build', this.package];
if (this.deps) {
args.push('--deps');
}
await this.cli.run(args);
}
}

93
tools/cli/src/bundle.ts Normal file
View File

@@ -0,0 +1,93 @@
import webpack, { type Compiler, type Configuration } from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import { merge } from 'webpack-merge';
import { Option, PackageCommand } from './command';
import { createWebpackConfig } from './webpack';
function getChannel() {
const channel = process.env.BUILD_TYPE ?? 'canary';
switch (channel) {
case 'canary':
case 'beta':
case 'stable':
case 'internal':
return channel;
default: {
throw new Error(
`BUILD_TYPE must be one of canary, beta, stable, internal, received [${channel}]`
);
}
}
}
export class BundleCommand extends PackageCommand {
static override paths = [['bundle'], ['webpack'], ['pack'], ['bun']];
// bundle is not able to run with deps
override deps = false;
dev = Option.Boolean('--dev,-d', false, {
description: 'Run in Development mode',
});
async execute() {
this.logger.info(`Packing package ${this.package}...`);
const config = await this.getConfig();
const compiler = webpack(config);
if (this.dev) {
await this.start(compiler, config.devServer);
} else {
await this.build(compiler);
}
}
async getConfig() {
let config = createWebpackConfig(this.workspace.getPackage(this.package), {
mode: this.dev ? 'development' : 'production',
channel: getChannel(),
});
let configOverride: Configuration | undefined;
const overrideConfigPath = this.workspace
.getPackage(this.package)
.join('webpack.config.js');
if (overrideConfigPath.isFile()) {
const override = await import(overrideConfigPath.value);
configOverride = override.config ?? override.default;
}
if (configOverride) {
config = merge(config, configOverride);
}
return config;
}
async start(compiler: Compiler, config: Configuration['devServer']) {
const devServer = new WebpackDevServer(config, compiler);
await devServer.start();
}
async build(compiler: Compiler) {
compiler.run((error, stats) => {
if (error) {
console.error(error);
process.exit(1);
}
if (stats) {
if (stats.hasErrors()) {
console.error(stats.toString('errors-only'));
process.exit(1);
} else {
console.log(stats.toString('minimal'));
}
}
});
}
}

71
tools/cli/src/clean.ts Normal file
View File

@@ -0,0 +1,71 @@
import { rmSync } from 'node:fs';
import { exec } from '@affine-tools/utils/process';
import { Command, Option } from './command';
export class CleanCommand extends Command {
static override paths = [['clean']];
cleanDist = Option.Boolean('--dist', false);
cleanRustTarget = Option.Boolean('--rust', false);
cleanNodeModules = Option.Boolean('--node-modules', false);
all = Option.Boolean('--all,-a', false);
async execute() {
this.logger.info('Cleaning Workspace...');
if (this.all || this.cleanNodeModules) {
this.doCleanNodeModules();
}
if (this.all || this.cleanDist) {
this.doCleanDist();
}
if (this.all || this.cleanRustTarget) {
this.doCleanRust();
}
}
doCleanNodeModules() {
this.logger.info('Cleaning node_modules...');
const rootNodeModules = this.workspace.join('node_modules');
if (rootNodeModules.isDirectory()) {
this.logger.info(`Cleaning ${rootNodeModules}`);
rmSync(rootNodeModules.value, { recursive: true });
}
this.workspace.forEach(pkg => {
const nodeModules = pkg.nodeModulesPath;
if (nodeModules.isDirectory()) {
this.logger.info(`Cleaning ${nodeModules}`);
rmSync(nodeModules.value, { recursive: true });
}
});
this.logger.info('node_modules cleaned');
}
doCleanDist() {
this.logger.info('Cleaning dist...');
this.workspace.forEach(pkg => {
if (pkg.distPath.isDirectory()) {
this.logger.info(`Cleaning ${pkg.distPath}`);
rmSync(pkg.distPath.value, { recursive: true });
}
if (pkg.libPath.isDirectory()) {
this.logger.info(`Cleaning ${pkg.libPath}`);
rmSync(pkg.libPath.value, { recursive: true });
}
});
this.logger.info('dist cleaned');
}
doCleanRust() {
exec('', 'cargo clean');
}
}

67
tools/cli/src/codegen.ts Executable file
View File

@@ -0,0 +1,67 @@
import { readFileSync, writeFileSync } from 'node:fs';
import type { Path } from '@affine-tools/utils/path';
import { type BuiltInParserName, format } from 'prettier';
import { Command } from './command';
export class CodegenCommand extends Command {
static override paths = [['init'], ['i'], ['codegen']];
async execute() {
this.logger.info('Generating Workspace configs');
await this.generateWorkspaceFiles();
this.logger.info('Workspace configs generated');
}
async generateWorkspaceFiles() {
const filesToGenerate: [Path, () => string, BuiltInParserName?][] = [
[
this.workspace.join('tsconfig.project.json'),
this.workspace.genProjectTsConfig.bind(this.workspace),
'json',
],
[
this.workspace
.getPackage('@affine-tools/utils')
.join('src/workspace.gen.ts'),
this.workspace.genWorkspaceInfo.bind(this.workspace),
'typescript',
],
[this.workspace.join('oxlint.json'), this.genOxlintConfig, 'json'],
];
for (const [path, content, formatter] of filesToGenerate) {
this.logger.info(`Output: ${path}`);
let file = content();
if (formatter) {
file = await this.format(file, formatter);
}
writeFileSync(path.value, file);
}
}
format(content: string, parser: BuiltInParserName) {
const config = JSON.parse(
readFileSync(this.workspace.join('.prettierrc').value, 'utf-8')
);
return format(content, { parser, ...config });
}
genOxlintConfig = () => {
const json = JSON.parse(
readFileSync(this.workspace.join('oxlint.json').value, 'utf-8')
);
const ignoreList = readFileSync(
this.workspace.join('.prettierignore').value,
'utf-8'
)
.split('\n')
.filter(line => line.trim() && !line.startsWith('#'));
json['ignorePatterns'] = ignoreList;
return JSON.stringify(json, null, 2);
};
}

71
tools/cli/src/command.ts Normal file
View File

@@ -0,0 +1,71 @@
import { AliasToPackage } from '@affine-tools/utils/distribution';
import { Logger } from '@affine-tools/utils/logger';
import { type PackageName, Workspace } from '@affine-tools/utils/workspace';
import { Command as BaseCommand, Option } from 'clipanion';
import * as t from 'typanion';
import type { CliContext } from './context';
export abstract class Command extends BaseCommand<CliContext> {
get logger() {
// @ts-expect-error hack: Get the command name
return new Logger(this.constructor.paths[0][0]);
}
get workspace() {
return this.context.workspace;
}
}
export abstract class PackageCommand extends Command {
protected availablePackageNameArgs = (
Workspace.PackageNames as string[]
).concat(Array.from(AliasToPackage.keys()));
protected packageNameValidator = t.isOneOf(
this.availablePackageNameArgs.map(k => t.isLiteral(k))
);
protected packageNameOrAlias = Option.String('--package,-p', {
required: true,
validator: this.packageNameValidator,
description: 'The package name or alias to be run with',
});
get package(): PackageName {
return (
AliasToPackage.get(this.packageNameOrAlias as any) ??
(this.packageNameOrAlias as PackageName)
);
}
deps = Option.Boolean('--deps', false, {
description:
'Execute the same command in workspace dependencies, if defined.',
});
}
export abstract class PackagesCommand extends Command {
protected availablePackageNameArgs = (
Workspace.PackageNames as string[]
).concat(Array.from(AliasToPackage.keys()));
protected packageNameValidator = t.isOneOf(
this.availablePackageNameArgs.map(k => t.isLiteral(k))
);
protected packageNamesOrAliases = Option.Array('--package,-p', {
required: true,
validator: t.isArray(this.packageNameValidator),
});
get packages() {
return this.packageNamesOrAliases.map(
name => AliasToPackage.get(name as any) ?? name
);
}
deps = Option.Boolean('--deps', false, {
description:
'Execute the same command in workspace dependencies, if defined.',
});
}
export { Option };

View File

@@ -1,38 +0,0 @@
// @ts-check
const { join } = require('node:path');
const projectRoot = join(__dirname, '../../../..');
module.exports.projectRoot = projectRoot;
/**
*
* @param {string | undefined} distribution
* @returns string
*/
module.exports.getCwdFromDistribution = function getCwdFromDistribution(
distribution
) {
switch (distribution) {
case 'web':
case undefined:
case null:
return join(projectRoot, 'packages/frontend/apps/web');
case 'desktop':
return join(projectRoot, 'packages/frontend/apps/electron/renderer');
case 'admin':
return join(projectRoot, 'packages/frontend/admin');
case 'mobile':
return join(projectRoot, 'packages/frontend/apps/mobile');
case 'ios':
return join(projectRoot, 'packages/frontend/apps/ios');
case 'android':
return join(projectRoot, 'packages/frontend/apps/android');
default: {
throw new Error(
'DISTRIBUTION must be one of web, desktop, admin, mobile'
);
}
}
};

View File

@@ -1,9 +0,0 @@
export type BuildFlags = {
distribution: 'web' | 'desktop' | 'admin' | 'mobile' | 'ios' | 'android';
mode: 'development' | 'production';
channel: 'stable' | 'beta' | 'canary' | 'internal';
static: boolean;
coverage?: boolean;
localBlockSuite?: string;
entry?: string | { [key: string]: string };
};

6
tools/cli/src/context.ts Normal file
View File

@@ -0,0 +1,6 @@
import type { Workspace } from '@affine-tools/utils/workspace';
import type { BaseContext } from 'clipanion';
export interface CliContext extends BaseContext {
workspace: Workspace;
}

15
tools/cli/src/dev.ts Normal file
View File

@@ -0,0 +1,15 @@
import { PackageCommand } from './command';
export class DevCommand extends PackageCommand {
static override paths = [['dev'], ['d']];
async execute() {
const args = [this.package, 'dev'];
if (this.deps) {
args.push('--deps');
}
await this.cli.run(args);
}
}

135
tools/cli/src/run.ts Normal file
View File

@@ -0,0 +1,135 @@
import { Path } from '@affine-tools/utils/path';
import { execAsync } from '@affine-tools/utils/process';
import type { PackageName } from '@affine-tools/utils/workspace';
import { Option, PackageCommand } from './command';
interface RunScriptOptions {
includeDependencies?: boolean;
waitDependencies?: boolean;
}
const currentDir = Path.dir(import.meta.url);
const ignoreLoaderScripts = [
'vitest',
'vite',
'ts-node',
'prisma',
'cap',
'tsc',
/electron(?!-)/,
];
export class RunCommand extends PackageCommand {
static override paths = [[], ['run'], ['r']];
static override usage = PackageCommand.Usage({
description: 'AFFiNE Monorepo scripts',
details: `
\`affine web <script>\` Run any script defined in package's package.json
\`affine codegen\` Generate the required files if there are any package added or removed
\`affine clean\` Clean the output files of ts, cargo, webpack, etc.
\`affine bundle\` Bundle the packages
\`affine build\` A proxy for <-p package>'s \`build\` script
\`affine dev\` A proxy for <-p package>'s \`dev\` script
`,
examples: [
[`See detail of each command`, '$0 -h'],
[
`Run custom 'xxx' script defined in @affine/web's package.json`,
'$0 web xxx',
],
[`Run 'codegen' for workspace`, '$0 codegen'],
[`Clean tsbuild and dist under each package`, '$0 clean --ts --dist'],
[`Clean node_modules under each package`, '$0 clean --node-modules'],
[`Clean everything`, '$0 clean --all'],
[`Run 'build' script for @affine/web`, '$0 build -p web'],
[
`Run 'build' script for @affine/web with all deps prebuild before`,
'$0 build -p web --deps',
],
],
});
// we use positional arguments instead of options
protected override packageNameOrAlias: string = Option.String({
required: true,
validator: this.packageNameValidator,
});
args = Option.Proxy({ name: 'args', required: 1 });
async execute() {
await this.run(this.package, this.args, {
includeDependencies: this.deps,
waitDependencies: true,
});
}
async run(name: PackageName, args: string[], opts: RunScriptOptions = {}) {
opts = { includeDependencies: false, ...opts };
const pkg = this.workspace.getPackage(name);
const script = args[0];
const pkgScript = pkg.scripts[script];
let isPackageJsonScript = false;
let isAFFiNEScript = false;
if (pkgScript) {
isPackageJsonScript = true;
isAFFiNEScript = pkgScript.startsWith('affine ');
} else {
isAFFiNEScript = script.startsWith('affine ');
}
if (isPackageJsonScript && opts.includeDependencies) {
this.logger.info(
`Running [${script}] script in dependencies of ${pkg.name}...`
);
await Promise.all(
pkg.deps.map(dep => {
this.logger.info(`Running [${script}] script in ${dep.name}...`);
return this.run(dep.name, args, opts);
})
);
}
if (isPackageJsonScript) {
this.logger.info(`Running [${script}] script in ${pkg.name}...`);
}
if (isAFFiNEScript) {
await this.cli.run([
...pkgScript.split(' ').slice(1),
...args.slice(1),
'-p',
pkg.name,
]);
} else {
const script = pkgScript ?? args[0];
// very simple test for auto ts/mjs scripts
const isLoaderRequired = !ignoreLoaderScripts.some(ignore =>
new RegExp(ignore).test(script)
);
await execAsync(name, ['yarn', ...args], {
cwd: pkg.path.value,
...(isLoaderRequired
? {
env: {
NODE_OPTIONS: `--import=${currentDir.join('../register.js').toFileUrl()}`,
},
}
: {}),
});
}
}
}

View File

@@ -1,26 +0,0 @@
import { spawn } from 'node:child_process';
import { resolve } from 'node:path';
import { build } from 'vite';
import { projectRoot } from '../config/cwd.cjs';
const infraFilePath = resolve(
projectRoot,
'packages',
'infra',
'vite.config.ts'
);
export const buildInfra = async () => {
await build({
configFile: infraFilePath,
});
};
export const watchInfra = async () => {
spawn('vite', ['build', '--watch'], {
cwd: resolve(projectRoot, 'packages/common/infra'),
shell: true,
stdio: 'inherit',
});
};

View File

@@ -0,0 +1,197 @@
import { execSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import type { BUILD_CONFIG_TYPE } from '@affine/env/global';
import { Path, ProjectRoot } from '@affine-tools/utils/path';
import { Repository } from '@napi-rs/simple-git';
import HTMLPlugin from 'html-webpack-plugin';
import once from 'lodash-es/once';
import type { Compiler, WebpackPluginInstance } from 'webpack';
import webpack from 'webpack';
import type { BuildFlags } from './types.js';
export const getPublicPath = (
flags: BuildFlags,
BUILD_CONFIG: BUILD_CONFIG_TYPE
) => {
const { BUILD_TYPE } = process.env;
if (typeof process.env.PUBLIC_PATH === 'string') {
return process.env.PUBLIC_PATH;
}
if (
flags.mode === 'development' ||
BUILD_CONFIG.distribution === 'desktop' ||
BUILD_CONFIG.distribution === 'ios' ||
BUILD_CONFIG.distribution === 'android'
) {
return '/';
}
switch (BUILD_TYPE) {
case 'stable':
return 'https://prod.affineassets.com/';
case 'beta':
return 'https://beta.affineassets.com/';
default:
return 'https://dev.affineassets.com/';
}
};
const DESCRIPTION = `There can be more than Notion and Miro. AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together.`;
const gitShortHash = once(() => {
const { GITHUB_SHA } = process.env;
if (GITHUB_SHA) {
return GITHUB_SHA.substring(0, 9);
}
const repo = new Repository(ProjectRoot.path);
const shortSha = repo.head().target()?.substring(0, 9);
if (shortSha) {
return shortSha;
}
const sha = execSync(`git rev-parse --short HEAD`, {
encoding: 'utf-8',
}).trim();
return sha;
});
const currentDir = Path.dir(import.meta.url);
function getHTMLPluginOptions(
flags: BuildFlags,
BUILD_CONFIG: BUILD_CONFIG_TYPE
) {
const publicPath = getPublicPath(flags, BUILD_CONFIG);
const cdnOrigin = publicPath.startsWith('/')
? undefined
: new URL(publicPath).origin;
const templateParams = {
GIT_SHORT_SHA: gitShortHash(),
DESCRIPTION,
PRECONNECT: cdnOrigin
? `<link rel="preconnect" href="${cdnOrigin}" />`
: '',
VIEWPORT_FIT: BUILD_CONFIG.isMobileEdition ? 'cover' : 'auto',
};
return {
template: currentDir.join('template.html').toString(),
inject: 'body',
filename: 'index.html',
minify: false,
templateParameters: templateParams,
chunks: ['app'],
} satisfies HTMLPlugin.Options;
}
export function createShellHTMLPlugin(
flags: BuildFlags,
BUILD_CONFIG: BUILD_CONFIG_TYPE
) {
const htmlPluginOptions = getHTMLPluginOptions(flags, BUILD_CONFIG);
return new HTMLPlugin({
...htmlPluginOptions,
chunks: ['shell'],
filename: `shell.html`,
});
}
export function createHTMLPlugins(
flags: BuildFlags,
BUILD_CONFIG: BUILD_CONFIG_TYPE
): WebpackPluginInstance[] {
const publicPath = getPublicPath(flags, BUILD_CONFIG);
const globalErrorHandler = [
'js/global-error-handler.js',
readFileSync(currentDir.join('./error-handler.js').toString(), 'utf-8'),
];
const htmlPluginOptions = getHTMLPluginOptions(flags, BUILD_CONFIG);
return [
{
apply(compiler: Compiler) {
compiler.hooks.compilation.tap(
'assets-manifest-plugin',
compilation => {
HTMLPlugin.getHooks(compilation).beforeAssetTagGeneration.tap(
'assets-manifest-plugin',
arg => {
if (
!BUILD_CONFIG.isElectron &&
!compilation.getAsset(globalErrorHandler[0])
) {
compilation.emitAsset(
globalErrorHandler[0],
new webpack.sources.RawSource(globalErrorHandler[1])
);
arg.assets.js.unshift(
arg.assets.publicPath + globalErrorHandler[0]
);
}
if (!compilation.getAsset('assets-manifest.json')) {
compilation.emitAsset(
globalErrorHandler[0],
new webpack.sources.RawSource(globalErrorHandler[1])
);
compilation.emitAsset(
`assets-manifest.json`,
new webpack.sources.RawSource(
JSON.stringify(
{
...arg.assets,
js: arg.assets.js.map(file =>
file.substring(arg.assets.publicPath.length)
),
css: arg.assets.css.map(file =>
file.substring(arg.assets.publicPath.length)
),
gitHash:
htmlPluginOptions.templateParameters.GIT_SHORT_SHA,
description:
htmlPluginOptions.templateParameters.DESCRIPTION,
},
null,
2
)
),
{
immutable: false,
}
);
}
return arg;
}
);
}
);
},
},
new HTMLPlugin({
...htmlPluginOptions,
publicPath,
meta: {
'env:publicPath': publicPath,
},
}),
// selfhost html
new HTMLPlugin({
...htmlPluginOptions,
meta: {
'env:isSelfHosted': 'true',
'env:publicPath': '/',
},
filename: 'selfhost.html',
templateParameters: {
...htmlPluginOptions.templateParameters,
PRECONNECT: '',
},
}),
];
}

View File

@@ -1,32 +1,33 @@
import { join } from 'node:path'; import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import type { BUILD_CONFIG_TYPE } from '@affine/env/global'; import { getBuildConfig } from '@affine-tools/utils/build-config';
import { ProjectRoot } from '@affine-tools/utils/path';
import type { Package } from '@affine-tools/utils/workspace';
import { PerfseePlugin } from '@perfsee/webpack'; import { PerfseePlugin } from '@perfsee/webpack';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import { sentryWebpackPlugin } from '@sentry/webpack-plugin'; import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin'; import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin';
import CopyPlugin from 'copy-webpack-plugin'; import CopyPlugin from 'copy-webpack-plugin';
import { compact } from 'lodash-es'; import compact from 'lodash-es/compact';
import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import TerserPlugin from 'terser-webpack-plugin'; import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack'; import webpack from 'webpack';
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server'; import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
import { projectRoot } from '../config/cwd.cjs';
import type { BuildFlags } from '../config/index.js';
import { productionCacheGroups } from './cache-group.js'; import { productionCacheGroups } from './cache-group.js';
import { createHTMLPlugins, createShellHTMLPlugin } from './html-plugin.js';
import { WebpackS3Plugin } from './s3-plugin.js'; import { WebpackS3Plugin } from './s3-plugin.js';
import type { BuildFlags } from './types';
const require = createRequire(import.meta.url);
const cssnano = require('cssnano');
const IN_CI = !!process.env.CI; const IN_CI = !!process.env.CI;
export const rootPath = join(fileURLToPath(import.meta.url), '..', '..');
export const workspaceRoot = join(rootPath, '..', '..', '..');
const OptimizeOptionOptions: ( const OptimizeOptionOptions: (
buildFlags: BuildFlags flags: BuildFlags
) => webpack.Configuration['optimization'] = buildFlags => ({ ) => webpack.Configuration['optimization'] = flags => ({
minimize: buildFlags.mode === 'production', minimize: flags.mode === 'production',
minimizer: [ minimizer: [
new TerserPlugin({ new TerserPlugin({
minify: TerserPlugin.swcMinify, minify: TerserPlugin.swcMinify,
@@ -60,71 +61,48 @@ const OptimizeOptionOptions: (
}, },
}); });
export const getPublicPath = (buildFlags: BuildFlags) => { export function createWebpackConfig(
const { BUILD_TYPE } = process.env; pkg: Package,
if (typeof process.env.PUBLIC_PATH === 'string') { flags: BuildFlags
return process.env.PUBLIC_PATH; ): webpack.Configuration {
} const buildConfig = getBuildConfig(pkg, flags);
if (
buildFlags.mode === 'development' ||
process.env.COVERAGE ||
buildFlags.distribution === 'desktop' ||
buildFlags.distribution === 'ios' ||
buildFlags.distribution === 'android'
) {
return '/';
}
switch (BUILD_TYPE) {
case 'stable':
return 'https://prod.affineassets.com/';
case 'beta':
return 'https://beta.affineassets.com/';
default:
return 'https://dev.affineassets.com/';
}
};
export const createConfiguration: (
cwd: string,
buildFlags: BuildFlags,
buildConfig: BUILD_CONFIG_TYPE
) => webpack.Configuration = (cwd, buildFlags, buildConfig) => {
const config = { const config = {
name: 'affine', name: 'affine',
// to set a correct base path for the source map context: pkg.path.value,
context: cwd,
experiments: { experiments: {
topLevelAwait: true, topLevelAwait: true,
outputModule: false, outputModule: false,
syncWebAssembly: true, syncWebAssembly: true,
}, },
entry: {
app: pkg.entry ?? './src/index.tsx',
},
output: { output: {
environment: { environment: {
module: true, module: true,
dynamicImport: true, dynamicImport: true,
}, },
filename: filename:
buildFlags.mode === 'production' flags.mode === 'production'
? 'js/[name].[contenthash:8].js' ? 'js/[name].[contenthash:8].js'
: 'js/[name].js', : 'js/[name].js',
// In some cases webpack will emit files starts with "_" which is reserved in web extension. // In some cases webpack will emit files starts with "_" which is reserved in web extension.
chunkFilename: pathData => chunkFilename: pathData =>
pathData.chunk?.name?.endsWith?.('worker') pathData.chunk?.name?.endsWith?.('worker')
? 'js/[name].[contenthash:8].js' ? 'js/[name].[contenthash:8].js'
: buildFlags.mode === 'production' : flags.mode === 'production'
? 'js/chunk.[name].[contenthash:8].js' ? 'js/chunk.[name].[contenthash:8].js'
: 'js/chunk.[name].js', : 'js/chunk.[name].js',
assetModuleFilename: assetModuleFilename:
buildFlags.mode === 'production' flags.mode === 'production'
? 'assets/[name].[contenthash:8][ext][query]' ? 'assets/[name].[contenthash:8][ext][query]'
: '[name].[contenthash:8][ext]', : '[name].[contenthash:8][ext]',
devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]', devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]',
hotUpdateChunkFilename: 'hot/[id].[fullhash].js', hotUpdateChunkFilename: 'hot/[id].[fullhash].js',
hotUpdateMainFilename: 'hot/[runtime].[fullhash].json', hotUpdateMainFilename: 'hot/[runtime].[fullhash].json',
path: join(cwd, 'dist'), path: pkg.distPath.value,
clean: buildFlags.mode === 'production', clean: flags.mode === 'production',
globalObject: 'globalThis', globalObject: 'globalThis',
// NOTE(@forehalo): always keep it '/' // NOTE(@forehalo): always keep it '/'
publicPath: '/', publicPath: '/',
@@ -132,10 +110,10 @@ export const createConfiguration: (
}, },
target: ['web', 'es2022'], target: ['web', 'es2022'],
mode: buildFlags.mode, mode: flags.mode,
devtool: devtool:
buildFlags.mode === 'production' flags.mode === 'production'
? 'source-map' ? 'source-map'
: 'eval-cheap-module-source-map', : 'eval-cheap-module-source-map',
@@ -147,14 +125,13 @@ export const createConfiguration: (
}, },
extensions: ['.js', '.ts', '.tsx'], extensions: ['.js', '.ts', '.tsx'],
alias: { alias: {
yjs: join(workspaceRoot, 'node_modules', 'yjs'), yjs: ProjectRoot.join('node_modules', 'yjs').value,
lit: join(workspaceRoot, 'node_modules', 'lit'), lit: ProjectRoot.join('node_modules', 'lit').value,
'@preact/signals-core': join( '@preact/signals-core': ProjectRoot.join(
workspaceRoot,
'node_modules', 'node_modules',
'@preact', '@preact',
'signals-core' 'signals-core'
), ).value,
}, },
}, },
@@ -230,7 +207,7 @@ export const createConfiguration: (
transform: { transform: {
react: { react: {
runtime: 'automatic', runtime: 'automatic',
refresh: buildFlags.mode === 'development' && { refresh: flags.mode === 'development' && {
refreshReg: '$RefreshReg$', refreshReg: '$RefreshReg$',
refreshSig: '$RefreshSig$', refreshSig: '$RefreshSig$',
emitFullSignatures: true, emitFullSignatures: true,
@@ -263,7 +240,7 @@ export const createConfiguration: (
{ {
test: /\.css$/, test: /\.css$/,
use: [ use: [
buildFlags.mode === 'development' flags.mode === 'development'
? 'style-loader' ? 'style-loader'
: MiniCssExtractPlugin.loader, : MiniCssExtractPlugin.loader,
{ {
@@ -280,7 +257,25 @@ export const createConfiguration: (
loader: 'postcss-loader', loader: 'postcss-loader',
options: { options: {
postcssOptions: { postcssOptions: {
config: join(rootPath, 'webpack', 'postcss.config.cjs'), plugins: [
cssnano({
preset: [
'default',
{
convertValues: false,
},
],
}),
].concat(
pkg.join('tailwind.config.js').exists()
? [
require('tailwindcss')(
require(pkg.join('tailwind.config.js').path)
),
'autoprefixer',
]
: []
),
}, },
}, },
}, },
@@ -292,7 +287,7 @@ export const createConfiguration: (
}, },
plugins: compact([ plugins: compact([
IN_CI ? null : new webpack.ProgressPlugin({ percentBy: 'entries' }), IN_CI ? null : new webpack.ProgressPlugin({ percentBy: 'entries' }),
buildFlags.mode === 'development' flags.mode === 'development'
? new ReactRefreshWebpackPlugin({ ? new ReactRefreshWebpackPlugin({
overlay: false, overlay: false,
esModule: true, esModule: true,
@@ -305,7 +300,7 @@ export const createConfiguration: (
}), }),
new VanillaExtractPlugin(), new VanillaExtractPlugin(),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(buildFlags.mode), 'process.env.NODE_ENV': JSON.stringify(flags.mode),
'process.env.CAPTCHA_SITE_KEY': JSON.stringify( 'process.env.CAPTCHA_SITE_KEY': JSON.stringify(
process.env.CAPTCHA_SITE_KEY process.env.CAPTCHA_SITE_KEY
), ),
@@ -323,24 +318,19 @@ export const createConfiguration: (
{} as Record<string, string> {} as Record<string, string>
), ),
}), }),
buildFlags.distribution === 'admin' buildConfig.isAdmin
? null ? null
: new CopyPlugin({ : new CopyPlugin({
patterns: [ patterns: [
{ {
// copy the shared public assets into dist // copy the shared public assets into dist
from: join( from: pkg.workspace.getPackage('@affine/core').join('public')
workspaceRoot, .value,
'packages', to: pkg.distPath.value,
'frontend',
'core',
'public'
),
to: join(cwd, 'dist'),
}, },
], ],
}), }),
buildFlags.mode === 'production' && flags.mode === 'production' &&
(buildConfig.isWeb || buildConfig.isMobileWeb || buildConfig.isAdmin) && (buildConfig.isWeb || buildConfig.isMobileWeb || buildConfig.isAdmin) &&
process.env.R2_SECRET_ACCESS_KEY process.env.R2_SECRET_ACCESS_KEY
? new WebpackS3Plugin() ? new WebpackS3Plugin()
@@ -349,14 +339,12 @@ export const createConfiguration: (
stats: { stats: {
errorDetails: true, errorDetails: true,
}, },
optimization: OptimizeOptionOptions(flags),
optimization: OptimizeOptionOptions(buildFlags),
devServer: { devServer: {
host: '0.0.0.0', host: '0.0.0.0',
allowedHosts: 'all', allowedHosts: 'all',
hot: buildFlags.static ? false : 'only', hot: true,
liveReload: !buildFlags.static, liveReload: true,
client: { client: {
overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined, overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined,
}, },
@@ -374,20 +362,10 @@ export const createConfiguration: (
}, },
static: [ static: [
{ {
directory: join( directory: pkg.workspace.getPackage('@affine/core').join('public')
projectRoot, .value,
'packages',
'frontend',
'core',
'public'
),
publicPath: '/', publicPath: '/',
watch: !buildFlags.static, watch: true,
},
{
directory: join(cwd, 'public'),
publicPath: '/',
watch: !buildFlags.static,
}, },
], ],
proxy: [ proxy: [
@@ -404,7 +382,7 @@ export const createConfiguration: (
} as DevServerConfiguration, } as DevServerConfiguration,
} satisfies webpack.Configuration; } satisfies webpack.Configuration;
if (buildFlags.mode === 'production' && process.env.PERFSEE_TOKEN) { if (flags.mode === 'production' && process.env.PERFSEE_TOKEN) {
config.plugins.push( config.plugins.push(
new PerfseePlugin({ new PerfseePlugin({
project: 'affine-toeverything', project: 'affine-toeverything',
@@ -412,7 +390,7 @@ export const createConfiguration: (
); );
} }
if (buildFlags.mode === 'development') { if (flags.mode === 'development') {
config.optimization = { config.optimization = {
...config.optimization, ...config.optimization,
minimize: false, minimize: false,
@@ -456,5 +434,11 @@ export const createConfiguration: (
); );
} }
config.plugins = config.plugins.concat(createHTMLPlugins(flags, buildConfig));
if (buildConfig.isElectron) {
config.plugins.push(createShellHTMLPlugin(flags, buildConfig));
}
return config; return config;
}; }

View File

@@ -1,47 +0,0 @@
const { join } = require('node:path');
const cssnano = require('cssnano');
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');
const { getCwdFromDistribution } = require('../config/cwd.cjs');
const projectCwd = getCwdFromDistribution(process.env.DISTRIBUTION);
const twConfig = (function () {
try {
const config = require(`${projectCwd}/tailwind.config.js`);
const { content } = config;
if (Array.isArray(content)) {
config.content = content.map(c =>
c.startsWith(projectCwd) ? c : join(projectCwd, c)
);
}
return config;
} catch {
return null;
}
})();
module.exports = function (context) {
const plugins = [
cssnano({
preset: [
'default',
{
convertValues: false,
},
],
}),
];
if (twConfig) {
plugins.push(tailwindcss(twConfig), autoprefixer());
}
return {
from: context.from,
plugins,
to: context.to,
};
};

View File

@@ -15,9 +15,7 @@ export class WebpackS3Plugin implements WebpackPluginInstance {
region: 'auto', region: 'auto',
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`, endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: { credentials: {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
accessKeyId: process.env.R2_ACCESS_KEY_ID!, accessKeyId: process.env.R2_ACCESS_KEY_ID!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!, secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
}, },
}); });

View File

@@ -0,0 +1,4 @@
export interface BuildFlags {
mode: 'development' | 'production';
channel: 'stable' | 'beta' | 'canary' | 'internal';
}

View File

@@ -1,183 +0,0 @@
import { execSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import type { BuildFlags } from '@affine/cli/config';
import { Repository } from '@napi-rs/simple-git';
import HTMLPlugin from 'html-webpack-plugin';
import { once } from 'lodash-es';
import type { Compiler } from 'webpack';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import {
createConfiguration,
getPublicPath,
rootPath,
workspaceRoot,
} from './config.js';
import { getBuildConfig } from './runtime-config.js';
const DESCRIPTION = `There can be more than Notion and Miro. AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together.`;
const gitShortHash = once(() => {
const { GITHUB_SHA } = process.env;
if (GITHUB_SHA) {
return GITHUB_SHA.substring(0, 9);
}
const repo = new Repository(workspaceRoot);
const shortSha = repo.head().target()?.substring(0, 9);
if (shortSha) {
return shortSha;
}
const sha = execSync(`git rev-parse --short HEAD`, {
encoding: 'utf-8',
}).trim();
return sha;
});
export function createWebpackConfig(cwd: string, flags: BuildFlags) {
console.log('build flags', flags);
const runtimeConfig = getBuildConfig(flags);
console.log('BUILD_CONFIG', runtimeConfig);
const config = createConfiguration(cwd, flags, runtimeConfig);
const entry =
typeof flags.entry === 'string' || !flags.entry
? {
app: flags.entry ?? resolve(cwd, 'src/index.tsx'),
}
: flags.entry;
const publicPath = getPublicPath(flags);
const cdnOrigin = publicPath.startsWith('/')
? undefined
: new URL(publicPath).origin;
const globalErrorHandler = [
'js/global-error-handler.js',
readFileSync(
join(workspaceRoot, 'tools/cli/src/webpack/error-handler.js'),
'utf-8'
),
];
const templateParams = {
GIT_SHORT_SHA: gitShortHash(),
DESCRIPTION,
PRECONNECT: cdnOrigin
? `<link rel="preconnect" href="${cdnOrigin}" />`
: '',
VIEWPORT_FIT:
flags.distribution === 'mobile' ||
flags.distribution === 'ios' ||
flags.distribution === 'android'
? 'cover'
: 'auto',
};
const createHTMLPlugins = (entryName: string) => {
const htmlPluginOptions = {
template: join(rootPath, 'webpack', 'template.html'),
inject: 'body',
filename: 'index.html',
minify: false,
templateParameters: templateParams,
chunks: [entryName],
} satisfies HTMLPlugin.Options;
if (entryName === 'app') {
return [
{
apply(compiler: Compiler) {
compiler.hooks.compilation.tap(
'assets-manifest-plugin',
compilation => {
HTMLPlugin.getHooks(compilation).beforeAssetTagGeneration.tap(
'assets-manifest-plugin',
arg => {
if (
flags.distribution !== 'desktop' &&
!compilation.getAsset(globalErrorHandler[0])
) {
compilation.emitAsset(
globalErrorHandler[0],
new webpack.sources.RawSource(globalErrorHandler[1])
);
arg.assets.js.unshift(
arg.assets.publicPath + globalErrorHandler[0]
);
}
if (!compilation.getAsset('assets-manifest.json')) {
compilation.emitAsset(
globalErrorHandler[0],
new webpack.sources.RawSource(globalErrorHandler[1])
);
compilation.emitAsset(
`assets-manifest.json`,
new webpack.sources.RawSource(
JSON.stringify(
{
...arg.assets,
js: arg.assets.js.map(file =>
file.substring(arg.assets.publicPath.length)
),
css: arg.assets.css.map(file =>
file.substring(arg.assets.publicPath.length)
),
gitHash: templateParams.GIT_SHORT_SHA,
description: templateParams.DESCRIPTION,
},
null,
2
)
),
{
immutable: false,
}
);
}
return arg;
}
);
}
);
},
},
new HTMLPlugin({
...htmlPluginOptions,
publicPath,
meta: {
'env:publicPath': publicPath,
},
}),
// selfhost html
new HTMLPlugin({
...htmlPluginOptions,
meta: {
'env:isSelfHosted': 'true',
'env:publicPath': '/',
},
filename: 'selfhost.html',
templateParameters: {
...htmlPluginOptions.templateParameters,
PRECONNECT: '',
},
}),
];
} else {
return [
new HTMLPlugin({
...htmlPluginOptions,
filename: `${entryName}.html`,
}),
];
}
};
return merge(config, {
entry,
plugins: Object.keys(entry).map(createHTMLPlugins).flat(),
});
}

View File

@@ -2,11 +2,12 @@
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"allowJs": true, "rootDir": "./src",
"module": "ESNext", "outDir": "./dist"
"moduleResolution": "Node",
"outDir": "lib"
}, },
"include": ["src", "package.json"], "include": ["./src"],
"references": [{ "path": "../../packages/common/env" }] "references": [
{ "path": "../../packages/common/env" },
{ "path": "../utils" }
]
} }

View File

@@ -14,12 +14,9 @@
"ios", "ios",
"android", "android",
"docs", "docs",
"storybook",
"component", "component",
"workspace",
"env", "env",
"graphql", "graphql",
"cli",
"hooks", "hooks",
"i18n", "i18n",
"native", "native",
@@ -27,7 +24,8 @@
"debug", "debug",
"nbstore", "nbstore",
"infra", "infra",
"editor" "editor",
"tools"
] ]
] ]
} }

View File

@@ -1,8 +1,7 @@
import { execSync } from 'node:child_process'; import { execSync } from 'node:child_process';
import fs from 'node:fs'; import fs from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Package } from '@affine-tools/utils/workspace';
import { import {
androidpublisher_v3, androidpublisher_v3,
auth as google_auth, auth as google_auth,
@@ -55,13 +54,10 @@ export async function fetchVersionCode(applicationId: string): Promise<number> {
} }
const versionCodeRegexPattern = /(versionCode(?:\s|=)*)(.*)/; const versionCodeRegexPattern = /(versionCode(?:\s|=)*)(.*)/;
const gradlePath = join(
fileURLToPath(import.meta.url), const gradlePath = new Package('@affine/android').join(
'..', 'App/app/build.gradle'
'..', ).value;
'..',
'packages/frontend/apps/android/App/app/build.gradle'
);
let gradleVersionCode = 0; let gradleVersionCode = 0;

View File

@@ -9,6 +9,7 @@
"bump": "node --import @oxc-node/core/register index.ts" "bump": "node --import @oxc-node/core/register index.ts"
}, },
"dependencies": { "dependencies": {
"@affine-tools/utils": "workspace:*",
"@googleapis/androidpublisher": "^22.0.0", "@googleapis/androidpublisher": "^22.0.0",
"@oxc-node/core": "^0.0.16" "@oxc-node/core": "^0.0.16"
}, },

View File

@@ -6,5 +6,6 @@
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Bundler" "moduleResolution": "Bundler"
}, },
"exclude": ["dist"] "exclude": ["dist"],
"include": ["index.ts"]
} }

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