diff --git a/.github/deployment/self-host/compose.yaml b/.github/deployment/self-host/compose.yaml new file mode 100644 index 0000000000..dde7dc8ba0 --- /dev/null +++ b/.github/deployment/self-host/compose.yaml @@ -0,0 +1,60 @@ +services: + affine: + image: ghcr.io/toeverything/affine-graphql:stable + container_name: affine_selfhosted + command: + ['sh', '-c', 'node ./scripts/self-host-predeploy && node ./dist/index.js'] + ports: + - '3010:3010' + - '5555:5555' + depends_on: + redis: + condition: service_healthy + postgres: + condition: service_healthy + volumes: + # custom configurations + - ~/.affine/self-host/config:/root/.affine/config + # blob storage + - ~/.affine/self-host/storage:/root/.affine/storage + logging: + driver: 'json-file' + options: + max-size: '1000m' + restart: unless-stopped + environment: + - NODE_OPTIONS="--import=./scripts/register.js" + - AFFINE_CONFIG_PATH=/root/.affine/config + - REDIS_SERVER_HOST=redis + - DATABASE_URL=postgres://affine:affine@postgres:5432/affine + - NODE_ENV=production + # Telemetry allows us to collect data on how you use the affine. This data will helps us improve the app and provide better features. + # Uncomment next line if you wish to quit telemetry. + # - TELEMETRY_ENABLE=false + redis: + image: redis + container_name: affine_redis + restart: unless-stopped + volumes: + - ~/.affine/self-host/redis:/data + healthcheck: + test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping'] + interval: 10s + timeout: 5s + retries: 5 + postgres: + image: postgres:16 + container_name: affine_postgres + restart: unless-stopped + volumes: + - ~/.affine/self-host/postgres:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U affine'] + interval: 10s + timeout: 5s + retries: 5 + environment: + POSTGRES_USER: affine + POSTGRES_PASSWORD: affine + POSTGRES_DB: affine + PGDATA: /var/lib/postgresql/data/pgdata diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index 32b6b88f4e..644bbe5c0c 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -13,6 +13,31 @@ permissions: packages: 'write' jobs: + build-server: + name: Build Server + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Version + id: version + uses: ./.github/actions/setup-version + - name: Setup Node.js + uses: ./.github/actions/setup-node + with: + electron-install: false + extra-flags: workspaces focus @affine/server @types/affine__env + - name: Build Server + run: | + find packages/backend/server/src -type d -name "__tests__" -exec rm -rf {} + + rm -rf packages/backend/server/src/seed + yarn workspace @affine/server build + - name: Upload server dist + uses: actions/upload-artifact@v4 + with: + name: server-dist + path: ./packages/backend/server/dist + if-no-files-found: error + build-web: name: Build @affine/web runs-on: ubuntu-latest @@ -118,7 +143,7 @@ jobs: matrix: targets: - name: x86_64-unknown-linux-gnu - file: server-native.x64.node + file: server-native.node - name: aarch64-unknown-linux-gnu file: server-native.arm64.node - name: armv7-unknown-linux-gnueabihf @@ -139,46 +164,11 @@ jobs: with: target: ${{ matrix.targets.name }} package: '@affine/server-native' - - name: Rename ${{ matrix.targets.file }} - run: | - mv ./packages/backend/native/server-native.node ./packages/backend/native/${{ matrix.targets.file }} - name: Upload ${{ matrix.targets.file }} uses: actions/upload-artifact@v4 with: - name: server-native-${{ matrix.targets.file }} - path: ./packages/backend/native/${{ matrix.targets.file }} - if-no-files-found: error - - build-server: - name: Build Server - runs-on: ubuntu-latest - needs: - - build-server-native - steps: - - uses: actions/checkout@v4 - - name: Setup Version - id: version - uses: ./.github/actions/setup-version - - name: Setup Node.js - uses: ./.github/actions/setup-node - with: - electron-install: false - extra-flags: workspaces focus @affine/server @types/affine__env - - name: Download server-native - uses: actions/download-artifact@v4 - with: - pattern: server-native-* - merge-multiple: true - path: ./packages/backend/native - - name: List server-native files - run: ls -alh ./packages/backend/native - - name: Build Server - run: yarn workspace @affine/server build - - name: Upload server dist - uses: actions/upload-artifact@v4 - with: - name: server-dist - path: ./packages/backend/server/dist + name: ${{ matrix.targets.file }} + path: ./packages/backend/native/server-native.node if-no-files-found: error build-images: @@ -189,6 +179,7 @@ jobs: - build-web - build-mobile - build-admin + - build-server-native steps: - uses: actions/checkout@v4 - name: Download server dist @@ -196,6 +187,25 @@ jobs: with: name: server-dist path: ./packages/backend/server/dist + - name: Download server-native.node + uses: actions/download-artifact@v4 + with: + name: server-native.node + path: ./packages/backend/server + - name: Download server-native.node arm64 + uses: actions/download-artifact@v4 + with: + name: server-native.arm64.node + path: ./packages/backend/native + - name: Download server-native.node arm64 + uses: actions/download-artifact@v4 + with: + name: server-native.armv7.node + path: . + - name: move server-native files + run: | + mv ./packages/backend/native/server-native.node ./packages/backend/server/server-native.arm64.node + mv server-native.node ./packages/backend/server/server-native.armv7.node - name: Setup env run: | echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV" diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 34e1a8b408..008ed9051a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -582,7 +582,7 @@ jobs: uses: actions/download-artifact@v4 with: name: server-native.node - path: ./packages/backend/native + path: ./packages/backend/server - name: Prepare Server Test Environment uses: ./.github/actions/server-test-env @@ -644,7 +644,7 @@ jobs: uses: actions/download-artifact@v4 with: name: server-native.node - path: ./packages/backend/native + path: ./packages/backend/server - name: Prepare Server Test Environment uses: ./.github/actions/server-test-env @@ -885,7 +885,7 @@ jobs: uses: actions/download-artifact@v4 with: name: server-native.node - path: ./packages/backend/native + path: ./packages/backend/server - name: Prepare Server Test Environment if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }} @@ -982,7 +982,7 @@ jobs: uses: actions/download-artifact@v4 with: name: server-native.node - path: ./packages/backend/native + path: ./packages/backend/server - name: Prepare Server Test Environment if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }} @@ -1077,7 +1077,7 @@ jobs: uses: actions/download-artifact@v4 with: name: server-native.node - path: ./packages/backend/native + path: ./packages/backend/server - name: Download affine.linux-x64-gnu.node uses: actions/download-artifact@v4 diff --git a/.github/workflows/copilot-test.yml b/.github/workflows/copilot-test.yml index c50420b68b..8e78b95851 100644 --- a/.github/workflows/copilot-test.yml +++ b/.github/workflows/copilot-test.yml @@ -73,7 +73,7 @@ jobs: uses: actions/download-artifact@v4 with: name: server-native.node - path: ./packages/backend/native + path: ./packages/backend/server - name: Prepare Server Test Environment env: @@ -142,7 +142,7 @@ jobs: uses: actions/download-artifact@v4 with: name: server-native.node - path: ./packages/backend/native + path: ./packages/backend/server - name: Prepare Server Test Environment env: diff --git a/packages/backend/native/index.js b/packages/backend/native/index.js index 3a075e45b8..7a93344621 100644 --- a/packages/backend/native/index.js +++ b/packages/backend/native/index.js @@ -1,21 +1,15 @@ -/** @type {import('.')} */ -let binding; -try { - binding = require('./server-native.node'); -} catch { - binding = - process.arch === 'arm64' - ? require('./server-native.arm64.node') - : process.arch === 'arm' - ? require('./server-native.armv7.node') - : require('./server-native.x64.node'); -} +import { createRequire } from 'node:module'; -module.exports.mergeUpdatesInApplyWay = binding.mergeUpdatesInApplyWay; -module.exports.verifyChallengeResponse = binding.verifyChallengeResponse; -module.exports.mintChallengeResponse = binding.mintChallengeResponse; -module.exports.getMime = binding.getMime; -module.exports.Tokenizer = binding.Tokenizer; -module.exports.fromModelName = binding.fromModelName; -module.exports.htmlSanitize = binding.htmlSanitize; -module.exports.parseDoc = binding.parseDoc; +const require = createRequire(import.meta.url); + +/** @type {import('.')} */ +const binding = require('./server-native.node'); + +export const mergeUpdatesInApplyWay = binding.mergeUpdatesInApplyWay; +export const verifyChallengeResponse = binding.verifyChallengeResponse; +export const mintChallengeResponse = binding.mintChallengeResponse; +export const getMime = binding.getMime; +export const Tokenizer = binding.Tokenizer; +export const fromModelName = binding.fromModelName; +export const htmlSanitize = binding.htmlSanitize; +export const parseDoc = binding.parseDoc; diff --git a/packages/backend/native/package.json b/packages/backend/native/package.json index dfa425256f..d1182a2223 100644 --- a/packages/backend/native/package.json +++ b/packages/backend/native/package.json @@ -4,6 +4,7 @@ "engines": { "node": ">= 10.16.0 < 11 || >= 11.8.0" }, + "type": "module", "main": "./index.js", "module": "./index.js", "types": "index.d.ts", diff --git a/packages/backend/server/package.json b/packages/backend/server/package.json index c0e1bdf5c9..3ff76ab9b5 100644 --- a/packages/backend/server/package.json +++ b/packages/backend/server/package.json @@ -8,7 +8,7 @@ "run-test": "./scripts/run-test.ts" }, "scripts": { - "build": "affine bundle", + "build": "tsc -b", "dev": "nodemon ./src/index.ts", "dev:mail": "email dev -d src/mails", "test": "ava --concurrency 1 --serial", @@ -17,16 +17,14 @@ "test:copilot:coverage": "c8 ava --timeout=5m \"src/__tests__/copilot-*.spec.ts\"", "e2e": "cross-env TEST_MODE=e2e ava --serial", "e2e:coverage": "cross-env TEST_MODE=e2e c8 ava --serial", - "data-migration": "cross-env NODE_ENV=development SERVER_FLAVOR=script r ./src/index.ts", + "data-migration": "cross-env NODE_ENV=development r ./src/data/index.ts", "init": "yarn prisma migrate dev && yarn data-migration run", "seed": "r ./src/seed/index.ts", "genconfig": "r ./scripts/genconfig.ts", - "cli": "cross-env SERVER_FLAVOR=script node ./dist/main.js", - "predeploy": "yarn prisma migrate deploy && yarn cli run", + "predeploy": "yarn prisma migrate deploy && node --import ./scripts/register.js ./dist/data/index.js run", "postinstall": "prisma generate" }, "dependencies": { - "@affine/server-native": "workspace:*", "@ai-sdk/google": "^1.2.10", "@ai-sdk/openai": "^1.3.9", "@ai-sdk/perplexity": "^1.1.6", @@ -74,7 +72,6 @@ "ai": "^4.3.4", "bullmq": "^5.40.2", "cookie-parser": "^1.4.7", - "cross-env": "^7.0.3", "date-fns": "^4.0.0", "dotenv": "^16.4.7", "eventemitter2": "^6.4.9", @@ -120,6 +117,7 @@ "@affine-tools/cli": "workspace:*", "@affine-tools/utils": "workspace:*", "@affine/graphql": "workspace:*", + "@affine/server-native": "workspace:*", "@faker-js/faker": "^9.6.0", "@nestjs/testing": "patch:@nestjs/testing@npm%3A10.4.15#~/.yarn/patches/@nestjs-testing-npm-10.4.15-d591a1705a.patch", "@types/cookie-parser": "^1.4.8", @@ -140,6 +138,7 @@ "@types/supertest": "^6.0.2", "ava": "^6.2.0", "c8": "^10.1.3", + "cross-env": "^7.0.3", "nodemon": "^3.1.7", "react-email": "4.0.7", "sinon": "^20.0.0", diff --git a/packages/backend/server/scripts/loader.js b/packages/backend/server/scripts/loader.js new file mode 100644 index 0000000000..ce31fb5096 --- /dev/null +++ b/packages/backend/server/scripts/loader.js @@ -0,0 +1,11 @@ +import { create, createEsmHooks } from 'ts-node'; + +const service = create({ + experimentalSpecifierResolution: 'node', + transpileOnly: true, + logError: true, + skipProject: true, +}); +const hooks = createEsmHooks(service); + +export const resolve = hooks.resolve; diff --git a/packages/backend/server/scripts/register.js b/packages/backend/server/scripts/register.js new file mode 100644 index 0000000000..ce20ff5368 --- /dev/null +++ b/packages/backend/server/scripts/register.js @@ -0,0 +1,4 @@ +import { register } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +register('./scripts/loader.js', pathToFileURL('./')); diff --git a/packages/backend/server/src/__tests__/env.spec.ts b/packages/backend/server/src/__tests__/env.spec.ts index 970b464fd0..cd7f317dcf 100644 --- a/packages/backend/server/src/__tests__/env.spec.ts +++ b/packages/backend/server/src/__tests__/env.spec.ts @@ -111,7 +111,7 @@ test('should tell flavors correctly', t => { sync: true, renderer: true, doc: true, - script: false, + script: true, }); process.env.SERVER_FLAVOR = 'graphql'; @@ -122,15 +122,6 @@ test('should tell flavors correctly', t => { doc: false, script: false, }); - - process.env.SERVER_FLAVOR = 'script'; - t.deepEqual(new Env().flavors, { - graphql: false, - sync: false, - renderer: false, - doc: false, - script: true, - }); }); test('should tell selfhosted correctly', t => { diff --git a/packages/backend/server/src/server.ts b/packages/backend/server/src/app.ts similarity index 77% rename from packages/backend/server/src/server.ts rename to packages/backend/server/src/app.ts index 35a6b4c1e1..50df53b980 100644 --- a/packages/backend/server/src/server.ts +++ b/packages/backend/server/src/app.ts @@ -9,7 +9,6 @@ import { CloudThrottlerGuard, Config, GlobalExceptionFilter, - URLHelper, } from './base'; import { SocketIoAdapter } from './base/websocket'; import { AuthGuard } from './core/auth'; @@ -17,7 +16,7 @@ import { serverTimingAndCache } from './middleware/timing'; const OneMB = 1024 * 1024; -export async function run() { +export async function createApp() { const { AppModule } = await import('./app.module'); const app = await NestFactory.create(AppModule, { @@ -29,8 +28,7 @@ export async function run() { app.useBodyParser('raw', { limit: 100 * OneMB }); - const logger = app.get(AFFiNELogger); - app.useLogger(logger); + app.useLogger(app.get(AFFiNELogger)); const config = app.get(Config); if (config.server.path) { @@ -59,12 +57,5 @@ export async function run() { const adapter = new SocketIoAdapter(app); app.useWebSocketAdapter(adapter); - const url = app.get(URLHelper); - const listeningHost = '0.0.0.0'; - - await app.listen(config.server.port, listeningHost); - - logger.log(`AFFiNE Server is running in [${env.DEPLOYMENT_TYPE}] mode`); - logger.log(`Listening on http://${listeningHost}:${config.server.port}`); - logger.log(`And the public server should be recognized as ${url.home}`); + return app; } diff --git a/packages/backend/server/src/data/commands/create.ts b/packages/backend/server/src/data/commands/create.ts index 32ec689108..504dc7e365 100644 --- a/packages/backend/server/src/data/commands/create.ts +++ b/packages/backend/server/src/data/commands/create.ts @@ -1,5 +1,5 @@ -import { appendFileSync, writeFileSync } from 'node:fs'; -import { join, parse } from 'node:path'; +import { writeFileSync } from 'node:fs'; +import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { Logger } from '@nestjs/common'; @@ -45,21 +45,15 @@ export class CreateCommand extends CommandRunner { const timestamp = Date.now(); const content = this.createScript(upperFirst(camelCase(name)) + timestamp); - const migrationDir = join( - fileURLToPath(import.meta.url), - '../../migrations' - ); const fileName = `${timestamp}-${kebabCase(name)}.ts`; - const filePath = join(migrationDir, fileName); + const filePath = join( + fileURLToPath(import.meta.url), + '../../migrations', + fileName + ); this.logger.log(`Creating ${fileName}...`); writeFileSync(filePath, content); - const indexFile = join(migrationDir, 'index.ts'); - appendFileSync( - indexFile, - `export * from './${parse(fileName).name}';`, - 'utf-8' - ); this.logger.log(`Migration file created at ${filePath}`); this.logger.log('Done'); } diff --git a/packages/backend/server/src/data/commands/run.ts b/packages/backend/server/src/data/commands/run.ts index 4941d1b5f2..50f5016020 100644 --- a/packages/backend/server/src/data/commands/run.ts +++ b/packages/backend/server/src/data/commands/run.ts @@ -1,12 +1,15 @@ +import { readdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + import { Logger } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { PrismaClient } from '@prisma/client'; import { once } from 'lodash-es'; import { Command, CommandRunner } from 'nest-commander'; -import * as migrations from '../migrations'; - interface Migration { + file: string; name: string; always?: boolean; up: (db: PrismaClient, injector: ModuleRef) => Promise; @@ -14,15 +17,33 @@ interface Migration { } export const collectMigrations = once(async () => { - return Object.values(migrations).map(migration => { - return { - name: migration.name, - // @ts-expect-error optional - always: migration.always, - up: migration.up, - down: migration.down, - }; - }) as Migration[]; + const folder = join(fileURLToPath(import.meta.url), '../../migrations'); + + const migrationFiles = readdirSync(folder) + .filter(desc => + desc.endsWith(import.meta.url.endsWith('.ts') ? '.ts' : '.js') + ) + .map(desc => join(folder, desc)); + + migrationFiles.sort((a, b) => a.localeCompare(b)); + + const migrations: Migration[] = await Promise.all( + migrationFiles.map(async file => { + return import(pathToFileURL(file).href).then(mod => { + const migration = mod[Object.keys(mod)[0]]; + + return { + file, + name: migration.name, + always: migration.always, + up: migration.up, + down: migration.down, + }; + }); + }) + ); + + return migrations; }); @Command({ diff --git a/packages/backend/server/src/cli.ts b/packages/backend/server/src/data/index.ts similarity index 57% rename from packages/backend/server/src/cli.ts rename to packages/backend/server/src/data/index.ts index 6e347a9c16..3797a9badb 100644 --- a/packages/backend/server/src/cli.ts +++ b/packages/backend/server/src/data/index.ts @@ -1,12 +1,16 @@ import { Logger } from '@nestjs/common'; import { CommandFactory } from 'nest-commander'; -import { CliAppModule } from './data/app'; +async function bootstrap() { + process.env.SERVER_FLAVOR = 'script'; -export async function run() { + await import('../prelude'); + const { CliAppModule } = await import('./app'); await CommandFactory.run(CliAppModule, new Logger()).catch(e => { console.error(e); process.exit(1); }); process.exit(0); } + +await bootstrap(); diff --git a/packages/backend/server/src/data/migrations/index.ts b/packages/backend/server/src/data/migrations/index.ts deleted file mode 100644 index 8b0906b456..0000000000 --- a/packages/backend/server/src/data/migrations/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './0001-refresh-features'; -export * from './1698398506533-guid'; -export * from './1703756315970-unamed-account'; -export * from './1721299086340-refresh-unnamed-user'; -export * from './1732861452428-migrate-invite-status'; -export * from './1733125339942-universal-subscription'; -export * from './1738590347632-feature-redundant'; diff --git a/packages/backend/server/src/env.ts b/packages/backend/server/src/env.ts index ea2a76b5e9..c308e88d47 100644 --- a/packages/backend/server/src/env.ts +++ b/packages/backend/server/src/env.ts @@ -102,8 +102,7 @@ export class Env implements AppEnv { sync: this.isFlavor(Flavor.Sync), renderer: this.isFlavor(Flavor.Renderer), doc: this.isFlavor(Flavor.Doc), - // Script in a special flavor, return true only when it is set explicitly - script: this.FLAVOR === Flavor.Script, + script: this.isFlavor(Flavor.Script), }; } diff --git a/packages/backend/server/src/index.ts b/packages/backend/server/src/index.ts index 42806bd65c..a0354c8b0e 100644 --- a/packages/backend/server/src/index.ts +++ b/packages/backend/server/src/index.ts @@ -1,11 +1,20 @@ /// import './prelude'; -import { run as runCli } from './cli'; -import { run as runServer } from './server'; +import { Logger } from '@nestjs/common'; -if (env.flavors.script) { - await runCli(); -} else { - await runServer(); -} +import { createApp } from './app'; +import { Config, URLHelper } from './base'; + +const app = await createApp(); +const config = app.get(Config); +const url = app.get(URLHelper); +const listeningHost = '0.0.0.0'; + +await app.listen(config.server.port, listeningHost); + +const logger = new Logger('App'); + +logger.log(`AFFiNE Server is running in [${env.DEPLOYMENT_TYPE}] mode`); +logger.log(`Listening on http://${listeningHost}:${config.server.port}`); +logger.log(`And the public server should be recognized as ${url.home}`); diff --git a/packages/backend/server/src/native.ts b/packages/backend/server/src/native.ts index a436cafe6c..c41618b780 100644 --- a/packages/backend/server/src/native.ts +++ b/packages/backend/server/src/native.ts @@ -1,4 +1,17 @@ -import * as serverNativeModule from '@affine/server-native'; +import { createRequire } from 'node:module'; + +let serverNativeModule: typeof import('@affine/server-native'); +try { + serverNativeModule = await import('@affine/server-native'); +} catch { + const require = createRequire(import.meta.url); + serverNativeModule = + process.arch === 'arm64' + ? require('../server-native.arm64.node') + : process.arch === 'arm' + ? require('../server-native.armv7.node') + : require('../server-native.node'); +} export const mergeUpdatesInApplyWay = serverNativeModule.mergeUpdatesInApplyWay; diff --git a/packages/backend/server/tsconfig.json b/packages/backend/server/tsconfig.json index 6997a939f7..26fc078e19 100644 --- a/packages/backend/server/tsconfig.json +++ b/packages/backend/server/tsconfig.json @@ -12,9 +12,9 @@ }, "include": ["./src"], "references": [ - { "path": "../native" }, { "path": "../../../tools/cli" }, { "path": "../../../tools/utils" }, - { "path": "../../common/graphql" } + { "path": "../../common/graphql" }, + { "path": "../native" } ] } diff --git a/packages/common/infra/package.json b/packages/common/infra/package.json index db90fd0aa2..b048806b3c 100644 --- a/packages/common/infra/package.json +++ b/packages/common/infra/package.json @@ -34,6 +34,7 @@ "devDependencies": { "@affine/templates": "workspace:*", "@emotion/react": "^11.14.0", + "@swc/core": "^1.10.1", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.1.0", "@types/react": "^19.0.1", diff --git a/packages/frontend/core/package.json b/packages/frontend/core/package.json index 34fc18f3f1..a0847b3620 100644 --- a/packages/frontend/core/package.json +++ b/packages/frontend/core/package.json @@ -84,6 +84,7 @@ "zod": "^3.24.1" }, "devDependencies": { + "@swc/core": "^1.10.1", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.1.0", "@types/animejs": "^3.1.12", diff --git a/tools/cli/package.json b/tools/cli/package.json index 96e17bb53d..6b444993b5 100644 --- a/tools/cli/package.json +++ b/tools/cli/package.json @@ -21,7 +21,6 @@ "@napi-rs/simple-git": "^0.1.19", "@perfsee/webpack": "^1.13.0", "@sentry/webpack-plugin": "^3.0.0", - "@swc/core": "^1.10.1", "@tailwindcss/postcss": "^4.0.0", "@vanilla-extract/webpack-plugin": "^2.3.15", "autoprefixer": "^10.4.20", @@ -35,7 +34,6 @@ "lodash-es": "^4.17.21", "mime-types": "^3.0.0", "mini-css-extract-plugin": "^2.9.2", - "node-loader": "^2.1.0", "postcss": "^8.4.49", "postcss-loader": "^8.1.1", "prettier": "^3.4.2", diff --git a/tools/cli/src/bundle.ts b/tools/cli/src/bundle.ts index ed786b3604..df3194f556 100644 --- a/tools/cli/src/bundle.ts +++ b/tools/cli/src/bundle.ts @@ -10,16 +10,12 @@ import WebpackDevServer, { } from 'webpack-dev-server'; import { Option, PackageCommand } from './command'; -import { - createHTMLTargetConfig, - createNodeTargetConfig, - createWorkerTargetConfig, -} from './webpack'; +import { createHTMLTargetConfig, createWorkerTargetConfig } from './webpack'; -function getBaseWorkerConfigs(pkg: Package) { +function getBundleConfigs(pkg: Package) { const core = new Package('@affine/core'); - return [ + const workerConfigs = [ createWorkerTargetConfig( pkg, core.srcPath.join( @@ -35,9 +31,7 @@ function getBaseWorkerConfigs(pkg: Package) { core.srcPath.join('blocksuite/extensions/turbo-painter.worker.ts').value ), ]; -} -function getBundleConfigs(pkg: Package) { switch (pkg.name) { case '@affine/admin': { return [createHTMLTargetConfig(pkg, pkg.srcPath.join('index.tsx').value)]; @@ -46,7 +40,6 @@ function getBundleConfigs(pkg: Package) { case '@affine/mobile': case '@affine/ios': case '@affine/android': { - const workerConfigs = getBaseWorkerConfigs(pkg); workerConfigs.push( createWorkerTargetConfig( pkg, @@ -65,8 +58,6 @@ function getBundleConfigs(pkg: Package) { ]; } case '@affine/electron-renderer': { - const workerConfigs = getBaseWorkerConfigs(pkg); - return [ createHTMLTargetConfig( pkg, @@ -87,14 +78,10 @@ function getBundleConfigs(pkg: Package) { ...workerConfigs, ]; } - case '@affine/server': { - return [createNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value)]; - } } throw new Error(`Unsupported package: ${pkg.name}`); } - const IN_CI = !!process.env.CI; const httpProxyMiddlewareLogLevel = IN_CI ? 'silent' : 'error'; diff --git a/tools/cli/src/webpack/index.ts b/tools/cli/src/webpack/index.ts index 5f45c6a0f4..5d8f5908eb 100644 --- a/tools/cli/src/webpack/index.ts +++ b/tools/cli/src/webpack/index.ts @@ -2,7 +2,7 @@ import { createRequire } from 'node:module'; import path from 'node:path'; import { getBuildConfig } from '@affine-tools/utils/build-config'; -import { Path, ProjectRoot } from '@affine-tools/utils/path'; +import { ProjectRoot } from '@affine-tools/utils/path'; import { Package } from '@affine-tools/utils/workspace'; import { PerfseePlugin } from '@perfsee/webpack'; import { sentryWebpackPlugin } from '@sentry/webpack-plugin'; @@ -75,7 +75,10 @@ export function createHTMLTargetConfig( }, entry, output: { - environment: { module: true, dynamicImport: true }, + environment: { + module: true, + dynamicImport: true, + }, filename: buildConfig.debug ? 'js/[name].js' : 'js/[name].[contenthash:8].js', @@ -124,7 +127,12 @@ export function createHTMLTargetConfig( }, //#region rules rules: [ - { test: /\.m?js?$/, resolve: { fullySpecified: false } }, + { + test: /\.m?js?$/, + resolve: { + fullySpecified: false, + }, + }, { test: /\.js$/, enforce: 'pre', @@ -177,7 +185,9 @@ export function createHTMLTargetConfig( target: 'es2022', externalHelpers: false, transform: { - react: { runtime: 'automatic' }, + react: { + runtime: 'automatic', + }, useDefineForClassFields: false, decoratorVersion: '2022-03', }, @@ -190,9 +200,18 @@ export function createHTMLTargetConfig( test: /\.(png|jpg|gif|svg|webp|mp4|zip)$/, type: 'asset/resource', }, - { test: /\.(ttf|eot|woff|woff2)$/, type: 'asset/resource' }, - { test: /\.txt$/, type: 'asset/source' }, - { test: /\.inline\.svg$/, type: 'asset/inline' }, + { + test: /\.(ttf|eot|woff|woff2)$/, + type: 'asset/resource', + }, + { + test: /\.txt$/, + type: 'asset/source', + }, + { + test: /\.inline\.svg$/, + type: 'asset/inline', + }, { test: /\.css$/, use: [ @@ -223,7 +242,12 @@ export function createHTMLTargetConfig( ] : [ cssnano({ - preset: ['default', { convertValues: false }], + preset: [ + 'default', + { + convertValues: false, + }, + ], }), ], }, @@ -274,7 +298,9 @@ export function createHTMLTargetConfig( new WebpackS3Plugin(), !buildConfig.debug && process.env.PERFSEE_TOKEN && - new PerfseePlugin({ project: 'affine-toeverything' }), + new PerfseePlugin({ + project: 'affine-toeverything', + }), process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT && @@ -299,7 +325,9 @@ export function createHTMLTargetConfig( ]), //#endregion - stats: { errorDetails: true }, + stats: { + errorDetails: true, + }, //#region optimization optimization: { @@ -311,8 +339,12 @@ export function createHTMLTargetConfig( extractComments: true, terserOptions: { ecma: 2020, - compress: { unused: true }, - mangle: { keep_classnames: true }, + compress: { + unused: true, + }, + mangle: { + keep_classnames: true, + }, }, }), ], @@ -321,7 +353,9 @@ export function createHTMLTargetConfig( usedExports: true, sideEffects: true, removeAvailableModules: true, - runtimeChunk: { name: 'runtime' }, + runtimeChunk: { + name: 'runtime', + }, splitChunks: { chunks: 'all', minSize: 1, @@ -348,7 +382,11 @@ export function createHTMLTargetConfig( priority: -10, reuseExistingChunk: true, }, - default: { minChunks: 2, priority: -20, reuseExistingChunk: true }, + default: { + minChunks: 2, + priority: -20, + reuseExistingChunk: true, + }, styles: { name: 'styles', type: 'css/mini-extract', @@ -378,7 +416,9 @@ export function createWorkerTargetConfig( outputModule: false, syncWebAssembly: true, }, - entry: { [workerName]: entry }, + entry: { + [workerName]: entry, + }, output: { filename: `js/${workerName}-${buildConfig.appVersion}.worker.js`, path: pkg.distPath.value, @@ -392,9 +432,14 @@ export function createWorkerTargetConfig( devtool: buildConfig.debug ? 'cheap-module-source-map' : 'source-map', resolve: { symlinks: true, - extensionAlias: { '.js': ['.js', '.ts'], '.mjs': ['.mjs', '.mts'] }, + extensionAlias: { + '.js': ['.js', '.ts'], + '.mjs': ['.mjs', '.mts'], + }, extensions: ['.js', '.ts'], - alias: { yjs: ProjectRoot.join('node_modules', 'yjs').value }, + alias: { + yjs: ProjectRoot.join('node_modules', 'yjs').value, + }, }, module: { @@ -409,7 +454,12 @@ export function createWorkerTargetConfig( }, }, rules: [ - { test: /\.m?js?$/, resolve: { fullySpecified: false } }, + { + test: /\.m?js?$/, + resolve: { + fullySpecified: false, + }, + }, { test: /\.js$/, enforce: 'pre', @@ -458,7 +508,9 @@ export function createWorkerTargetConfig( {} as Record ) ), - new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }), + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT && @@ -468,7 +520,9 @@ export function createWorkerTargetConfig( authToken: process.env.SENTRY_AUTH_TOKEN, }), ]), - stats: { errorDetails: true }, + stats: { + errorDetails: true, + }, optimization: { minimize: !buildConfig.debug, minimizer: [ @@ -478,8 +532,12 @@ export function createWorkerTargetConfig( extractComments: true, terserOptions: { ecma: 2020, - compress: { unused: true }, - mangle: { keep_classnames: true }, + compress: { + unused: true, + }, + mangle: { + keep_classnames: true, + }, }, }), ], @@ -491,120 +549,8 @@ export function createWorkerTargetConfig( runtimeChunk: false, splitChunks: false, }, - performance: { hints: false }, - }; -} - -export function createNodeTargetConfig( - pkg: Package, - entry: string -): Omit & { name: string } { - return { - name: entry, - context: ProjectRoot.value, - experiments: { - topLevelAwait: true, - outputModule: pkg.packageJson.type === 'module', - syncWebAssembly: true, - }, - entry: { index: entry }, - output: { - filename: `main.js`, - path: pkg.distPath.value, - clean: true, - globalObject: 'globalThis', - }, - target: ['node', 'es2022'], - externals: (data, callback) => { - if ( - data.request && - // import ... from 'module' - /^[a-zA-Z@]/.test(data.request) && - // not workspace deps - !pkg.deps.some(dep => data.request!.startsWith(dep.name)) - ) { - callback(null, true); - } else { - callback(null, false); - } - }, - externalsPresets: { node: true }, - node: { __dirname: false, __filename: false }, - mode: 'none', - devtool: 'source-map', - resolve: { - symlinks: true, - extensionAlias: { '.js': ['.js', '.ts'], '.mjs': ['.mjs', '.mts'] }, - extensions: ['.js', '.ts', '.tsx', '.node'], - alias: { yjs: ProjectRoot.join('node_modules', 'yjs').value }, - }, - module: { - parser: { - javascript: { url: false, importMeta: false, createRequire: false }, - }, - rules: [ - { - test: /\.js$/, - enforce: 'pre', - include: /@blocksuite/, - use: ['source-map-loader'], - }, - { - test: /\.node$/, - loader: Path.dir(import.meta.url).join('node-loader.js').value, - }, - { - test: /\.tsx?$/, - exclude: /node_modules/, - loader: 'swc-loader', - options: { - // https://swc.rs/docs/configuring-swc/ - jsc: { - preserveAllComments: true, - parser: { - syntax: 'typescript', - dynamicImport: true, - topLevelAwait: true, - tsx: true, - decorators: true, - }, - target: 'es2022', - externalHelpers: false, - transform: { - legacyDecorator: true, - decoratorMetadata: true, - react: { runtime: 'automatic' }, - }, - }, - sourceMaps: true, - inlineSourcesContent: true, - }, - }, - ], - }, - plugins: compact([ - new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }), - new webpack.IgnorePlugin({ - checkResource(resource) { - const lazyImports = [ - '@nestjs/microservices', - '@nestjs/websockets/socket-module', - '@apollo/subgraph', - '@apollo/gateway', - '@as-integrations/fastify', - 'ts-morph', - 'class-validator', - 'class-transformer', - ]; - return lazyImports.some(lazyImport => - resource.startsWith(lazyImport) - ); - }, - }), - ]), - stats: { errorDetails: true }, - optimization: { nodeEnv: false }, - performance: { hints: false }, - ignoreWarnings: [/^(?!CriticalDependenciesWarning$)/], + performance: { + hints: false, + }, }; } diff --git a/tools/cli/src/webpack/node-loader.js b/tools/cli/src/webpack/node-loader.js deleted file mode 100644 index 124a4cb500..0000000000 --- a/tools/cli/src/webpack/node-loader.js +++ /dev/null @@ -1,18 +0,0 @@ -import { parse } from 'node:path'; - -export const raw = true; -/** - * @type {import('webpack').LoaderDefinitionFunction} - */ -export default function loader(content) { - const name = parse(this.resourcePath).base; - this.emitFile(name, content); - - return ` - import { createRequire } from 'node:module' - - const require = createRequire(import.meta.url) - const binding = require('./${name}') - export default binding - `; -} diff --git a/tools/utils/src/types.ts b/tools/utils/src/types.ts index 0a72e46415..9a7fd1cc2e 100644 --- a/tools/utils/src/types.ts +++ b/tools/utils/src/types.ts @@ -8,12 +8,13 @@ export interface YarnWorkspaceItem { export interface CommonPackageJsonContent { name: string; - type?: 'module' | 'commonjs'; version: string; private?: boolean; dependencies?: { [key: string]: string }; devDependencies?: { [key: string]: string }; scripts?: { [key: string]: string }; main?: string; - exports?: { [key: string]: string | { [key: string]: string } }; + exports?: { + [key: string]: string; + }; } diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index 30e2d39408..fc2924e8f4 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -963,10 +963,10 @@ export const PackageList = [ location: 'packages/backend/server', name: '@affine/server', workspaceDependencies: [ - 'packages/backend/native', 'tools/cli', 'tools/utils', 'packages/common/graphql', + 'packages/backend/native', ], }, { diff --git a/yarn.lock b/yarn.lock index 1d2120f1ce..d0032b3ad7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -122,7 +122,6 @@ __metadata: "@napi-rs/simple-git": "npm:^0.1.19" "@perfsee/webpack": "npm:^1.13.0" "@sentry/webpack-plugin": "npm:^3.0.0" - "@swc/core": "npm:^1.10.1" "@tailwindcss/postcss": "npm:^4.0.0" "@types/lodash-es": "npm:^4.17.12" "@types/mime-types": "npm:^2.1.4" @@ -140,7 +139,6 @@ __metadata: lodash-es: "npm:^4.17.21" mime-types: "npm:^3.0.0" mini-css-extract-plugin: "npm:^2.9.2" - node-loader: "npm:^2.1.0" postcss: "npm:^8.4.49" postcss-loader: "npm:^8.1.1" prettier: "npm:^3.4.2" @@ -418,6 +416,7 @@ __metadata: "@radix-ui/react-slot": "npm:^1.1.1" "@radix-ui/react-toolbar": "npm:^1.1.1" "@sentry/react": "npm:^9.2.0" + "@swc/core": "npm:^1.10.1" "@testing-library/dom": "npm:^10.4.0" "@testing-library/react": "npm:^16.1.0" "@toeverything/infra": "workspace:*" @@ -14287,6 +14286,7 @@ __metadata: "@affine/templates": "workspace:*" "@emotion/react": "npm:^11.14.0" "@preact/signals-core": "npm:^1.8.0" + "@swc/core": "npm:^1.10.1" "@testing-library/dom": "npm:^10.4.0" "@testing-library/react": "npm:^16.1.0" "@types/react": "npm:^19.0.1" @@ -25145,7 +25145,7 @@ __metadata: languageName: node linkType: hard -"loader-utils@npm:^2.0.0, loader-utils@npm:^2.0.3": +"loader-utils@npm:^2.0.0": version: 2.0.4 resolution: "loader-utils@npm:2.0.4" dependencies: @@ -27407,17 +27407,6 @@ __metadata: languageName: node linkType: hard -"node-loader@npm:^2.1.0": - version: 2.1.0 - resolution: "node-loader@npm:2.1.0" - dependencies: - loader-utils: "npm:^2.0.3" - peerDependencies: - webpack: ^5.0.0 - checksum: 10/d2f20b1e0f946055fcbbf365c3927ffecfff9aee3b5cc2d71e45229ca27010267d3d6fdea04dcb7c0bc7ce9b87878105b8c1d15c05065c813b5c8ec5ef1fb4d1 - languageName: node - linkType: hard - "node-releases@npm:^2.0.19": version: 2.0.19 resolution: "node-releases@npm:2.0.19"