build: allow node package depends on workspace packages (#11892)

This commit is contained in:
forehalo
2025-04-23 10:04:58 +00:00
parent 64997d4a0e
commit c00671dd84
28 changed files with 326 additions and 325 deletions

View File

@@ -1,60 +0,0 @@
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

View File

@@ -13,31 +13,6 @@ permissions:
packages: 'write' packages: 'write'
jobs: 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: build-web:
name: Build @affine/web name: Build @affine/web
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -143,7 +118,7 @@ jobs:
matrix: matrix:
targets: targets:
- name: x86_64-unknown-linux-gnu - name: x86_64-unknown-linux-gnu
file: server-native.node file: server-native.x64.node
- name: aarch64-unknown-linux-gnu - name: aarch64-unknown-linux-gnu
file: server-native.arm64.node file: server-native.arm64.node
- name: armv7-unknown-linux-gnueabihf - name: armv7-unknown-linux-gnueabihf
@@ -164,11 +139,46 @@ jobs:
with: with:
target: ${{ matrix.targets.name }} target: ${{ matrix.targets.name }}
package: '@affine/server-native' 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 }} - name: Upload ${{ matrix.targets.file }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ matrix.targets.file }} name: server-native-${{ matrix.targets.file }}
path: ./packages/backend/native/server-native.node 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
if-no-files-found: error if-no-files-found: error
build-images: build-images:
@@ -179,7 +189,6 @@ jobs:
- build-web - build-web
- build-mobile - build-mobile
- build-admin - build-admin
- build-server-native
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Download server dist - name: Download server dist
@@ -187,25 +196,6 @@ jobs:
with: with:
name: server-dist name: server-dist
path: ./packages/backend/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 - name: Setup env
run: | run: |
echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV" echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"

View File

@@ -582,7 +582,7 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: server-native.node name: server-native.node
path: ./packages/backend/server path: ./packages/backend/native
- name: Prepare Server Test Environment - name: Prepare Server Test Environment
uses: ./.github/actions/server-test-env uses: ./.github/actions/server-test-env
@@ -644,7 +644,7 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: server-native.node name: server-native.node
path: ./packages/backend/server path: ./packages/backend/native
- name: Prepare Server Test Environment - name: Prepare Server Test Environment
uses: ./.github/actions/server-test-env uses: ./.github/actions/server-test-env
@@ -885,7 +885,7 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: server-native.node name: server-native.node
path: ./packages/backend/server path: ./packages/backend/native
- name: Prepare Server Test Environment - name: Prepare Server Test Environment
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }} if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
@@ -982,7 +982,7 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: server-native.node name: server-native.node
path: ./packages/backend/server path: ./packages/backend/native
- name: Prepare Server Test Environment - name: Prepare Server Test Environment
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }} if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
@@ -1077,7 +1077,7 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: server-native.node name: server-native.node
path: ./packages/backend/server path: ./packages/backend/native
- name: Download affine.linux-x64-gnu.node - name: Download affine.linux-x64-gnu.node
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4

View File

@@ -73,7 +73,7 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: server-native.node name: server-native.node
path: ./packages/backend/server path: ./packages/backend/native
- name: Prepare Server Test Environment - name: Prepare Server Test Environment
env: env:
@@ -142,7 +142,7 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: server-native.node name: server-native.node
path: ./packages/backend/server path: ./packages/backend/native
- name: Prepare Server Test Environment - name: Prepare Server Test Environment
env: env:

View File

@@ -1,15 +1,21 @@
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
/** @type {import('.')} */ /** @type {import('.')} */
const binding = require('./server-native.node'); 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');
}
export const mergeUpdatesInApplyWay = binding.mergeUpdatesInApplyWay; module.exports.mergeUpdatesInApplyWay = binding.mergeUpdatesInApplyWay;
export const verifyChallengeResponse = binding.verifyChallengeResponse; module.exports.verifyChallengeResponse = binding.verifyChallengeResponse;
export const mintChallengeResponse = binding.mintChallengeResponse; module.exports.mintChallengeResponse = binding.mintChallengeResponse;
export const getMime = binding.getMime; module.exports.getMime = binding.getMime;
export const Tokenizer = binding.Tokenizer; module.exports.Tokenizer = binding.Tokenizer;
export const fromModelName = binding.fromModelName; module.exports.fromModelName = binding.fromModelName;
export const htmlSanitize = binding.htmlSanitize; module.exports.htmlSanitize = binding.htmlSanitize;
export const parseDoc = binding.parseDoc; module.exports.parseDoc = binding.parseDoc;

View File

@@ -4,7 +4,6 @@
"engines": { "engines": {
"node": ">= 10.16.0 < 11 || >= 11.8.0" "node": ">= 10.16.0 < 11 || >= 11.8.0"
}, },
"type": "module",
"main": "./index.js", "main": "./index.js",
"module": "./index.js", "module": "./index.js",
"types": "index.d.ts", "types": "index.d.ts",

View File

@@ -8,7 +8,7 @@
"run-test": "./scripts/run-test.ts" "run-test": "./scripts/run-test.ts"
}, },
"scripts": { "scripts": {
"build": "tsc -b", "build": "affine bundle",
"dev": "nodemon ./src/index.ts", "dev": "nodemon ./src/index.ts",
"dev:mail": "email dev -d src/mails", "dev:mail": "email dev -d src/mails",
"test": "ava --concurrency 1 --serial", "test": "ava --concurrency 1 --serial",
@@ -17,14 +17,16 @@
"test:copilot:coverage": "c8 ava --timeout=5m \"src/__tests__/copilot-*.spec.ts\"", "test:copilot:coverage": "c8 ava --timeout=5m \"src/__tests__/copilot-*.spec.ts\"",
"e2e": "cross-env TEST_MODE=e2e ava --serial", "e2e": "cross-env TEST_MODE=e2e ava --serial",
"e2e:coverage": "cross-env TEST_MODE=e2e c8 ava --serial", "e2e:coverage": "cross-env TEST_MODE=e2e c8 ava --serial",
"data-migration": "cross-env NODE_ENV=development r ./src/data/index.ts", "data-migration": "cross-env NODE_ENV=development SERVER_FLAVOR=script r ./src/index.ts",
"init": "yarn prisma migrate dev && yarn data-migration run", "init": "yarn prisma migrate dev && yarn data-migration run",
"seed": "r ./src/seed/index.ts", "seed": "r ./src/seed/index.ts",
"genconfig": "r ./scripts/genconfig.ts", "genconfig": "r ./scripts/genconfig.ts",
"predeploy": "yarn prisma migrate deploy && node --import ./scripts/register.js ./dist/data/index.js run", "cli": "cross-env SERVER_FLAVOR=script node ./dist/main.js",
"predeploy": "yarn prisma migrate deploy && yarn cli run",
"postinstall": "prisma generate" "postinstall": "prisma generate"
}, },
"dependencies": { "dependencies": {
"@affine/server-native": "workspace:*",
"@ai-sdk/google": "^1.2.10", "@ai-sdk/google": "^1.2.10",
"@ai-sdk/openai": "^1.3.9", "@ai-sdk/openai": "^1.3.9",
"@ai-sdk/perplexity": "^1.1.6", "@ai-sdk/perplexity": "^1.1.6",
@@ -72,6 +74,7 @@
"ai": "^4.3.4", "ai": "^4.3.4",
"bullmq": "^5.40.2", "bullmq": "^5.40.2",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"cross-env": "^7.0.3",
"date-fns": "^4.0.0", "date-fns": "^4.0.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"eventemitter2": "^6.4.9", "eventemitter2": "^6.4.9",
@@ -117,7 +120,6 @@
"@affine-tools/cli": "workspace:*", "@affine-tools/cli": "workspace:*",
"@affine-tools/utils": "workspace:*", "@affine-tools/utils": "workspace:*",
"@affine/graphql": "workspace:*", "@affine/graphql": "workspace:*",
"@affine/server-native": "workspace:*",
"@faker-js/faker": "^9.6.0", "@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", "@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", "@types/cookie-parser": "^1.4.8",
@@ -138,7 +140,6 @@
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"ava": "^6.2.0", "ava": "^6.2.0",
"c8": "^10.1.3", "c8": "^10.1.3",
"cross-env": "^7.0.3",
"nodemon": "^3.1.7", "nodemon": "^3.1.7",
"react-email": "4.0.7", "react-email": "4.0.7",
"sinon": "^20.0.0", "sinon": "^20.0.0",

View File

@@ -1,11 +0,0 @@
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;

View File

@@ -1,4 +0,0 @@
import { register } from 'node:module';
import { pathToFileURL } from 'node:url';
register('./scripts/loader.js', pathToFileURL('./'));

View File

@@ -111,7 +111,7 @@ test('should tell flavors correctly', t => {
sync: true, sync: true,
renderer: true, renderer: true,
doc: true, doc: true,
script: true, script: false,
}); });
process.env.SERVER_FLAVOR = 'graphql'; process.env.SERVER_FLAVOR = 'graphql';
@@ -122,6 +122,15 @@ test('should tell flavors correctly', t => {
doc: false, doc: false,
script: 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 => { test('should tell selfhosted correctly', t => {

View File

@@ -1,16 +1,12 @@
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { CommandFactory } from 'nest-commander'; import { CommandFactory } from 'nest-commander';
async function bootstrap() { import { CliAppModule } from './data/app';
process.env.SERVER_FLAVOR = 'script';
await import('../prelude'); export async function run() {
const { CliAppModule } = await import('./app');
await CommandFactory.run(CliAppModule, new Logger()).catch(e => { await CommandFactory.run(CliAppModule, new Logger()).catch(e => {
console.error(e); console.error(e);
process.exit(1); process.exit(1);
}); });
process.exit(0); process.exit(0);
} }
await bootstrap();

View File

@@ -1,5 +1,5 @@
import { writeFileSync } from 'node:fs'; import { appendFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path'; import { join, parse } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
@@ -45,15 +45,21 @@ export class CreateCommand extends CommandRunner {
const timestamp = Date.now(); const timestamp = Date.now();
const content = this.createScript(upperFirst(camelCase(name)) + timestamp); const content = this.createScript(upperFirst(camelCase(name)) + timestamp);
const fileName = `${timestamp}-${kebabCase(name)}.ts`; const migrationDir = join(
const filePath = join(
fileURLToPath(import.meta.url), fileURLToPath(import.meta.url),
'../../migrations', '../../migrations'
fileName
); );
const fileName = `${timestamp}-${kebabCase(name)}.ts`;
const filePath = join(migrationDir, fileName);
this.logger.log(`Creating ${fileName}...`); this.logger.log(`Creating ${fileName}...`);
writeFileSync(filePath, content); 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(`Migration file created at ${filePath}`);
this.logger.log('Done'); this.logger.log('Done');
} }

View File

@@ -1,15 +1,12 @@
import { readdirSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { once } from 'lodash-es'; import { once } from 'lodash-es';
import { Command, CommandRunner } from 'nest-commander'; import { Command, CommandRunner } from 'nest-commander';
import * as migrations from '../migrations';
interface Migration { interface Migration {
file: string;
name: string; name: string;
always?: boolean; always?: boolean;
up: (db: PrismaClient, injector: ModuleRef) => Promise<void>; up: (db: PrismaClient, injector: ModuleRef) => Promise<void>;
@@ -17,33 +14,15 @@ interface Migration {
} }
export const collectMigrations = once(async () => { export const collectMigrations = once(async () => {
const folder = join(fileURLToPath(import.meta.url), '../../migrations'); return Object.values(migrations).map(migration => {
return {
const migrationFiles = readdirSync(folder) name: migration.name,
.filter(desc => // @ts-expect-error optional
desc.endsWith(import.meta.url.endsWith('.ts') ? '.ts' : '.js') always: migration.always,
) up: migration.up,
.map(desc => join(folder, desc)); down: migration.down,
};
migrationFiles.sort((a, b) => a.localeCompare(b)); }) as Migration[];
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({ @Command({

View File

@@ -0,0 +1,7 @@
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';

View File

@@ -102,7 +102,8 @@ export class Env implements AppEnv {
sync: this.isFlavor(Flavor.Sync), sync: this.isFlavor(Flavor.Sync),
renderer: this.isFlavor(Flavor.Renderer), renderer: this.isFlavor(Flavor.Renderer),
doc: this.isFlavor(Flavor.Doc), doc: this.isFlavor(Flavor.Doc),
script: this.isFlavor(Flavor.Script), // Script in a special flavor, return true only when it is set explicitly
script: this.FLAVOR === Flavor.Script,
}; };
} }

View File

@@ -1,20 +1,11 @@
/// <reference types="./global.d.ts" /> /// <reference types="./global.d.ts" />
import './prelude'; import './prelude';
import { Logger } from '@nestjs/common'; import { run as runCli } from './cli';
import { run as runServer } from './server';
import { createApp } from './app'; if (env.flavors.script) {
import { Config, URLHelper } from './base'; await runCli();
} else {
const app = await createApp(); await runServer();
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}`);

View File

@@ -1,17 +1,4 @@
import { createRequire } from 'node:module'; import * as serverNativeModule from '@affine/server-native';
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; export const mergeUpdatesInApplyWay = serverNativeModule.mergeUpdatesInApplyWay;

View File

@@ -9,6 +9,7 @@ import {
CloudThrottlerGuard, CloudThrottlerGuard,
Config, Config,
GlobalExceptionFilter, GlobalExceptionFilter,
URLHelper,
} from './base'; } from './base';
import { SocketIoAdapter } from './base/websocket'; import { SocketIoAdapter } from './base/websocket';
import { AuthGuard } from './core/auth'; import { AuthGuard } from './core/auth';
@@ -16,7 +17,7 @@ import { serverTimingAndCache } from './middleware/timing';
const OneMB = 1024 * 1024; const OneMB = 1024 * 1024;
export async function createApp() { export async function run() {
const { AppModule } = await import('./app.module'); const { AppModule } = await import('./app.module');
const app = await NestFactory.create<NestExpressApplication>(AppModule, { const app = await NestFactory.create<NestExpressApplication>(AppModule, {
@@ -28,7 +29,8 @@ export async function createApp() {
app.useBodyParser('raw', { limit: 100 * OneMB }); app.useBodyParser('raw', { limit: 100 * OneMB });
app.useLogger(app.get(AFFiNELogger)); const logger = app.get(AFFiNELogger);
app.useLogger(logger);
const config = app.get(Config); const config = app.get(Config);
if (config.server.path) { if (config.server.path) {
@@ -57,5 +59,12 @@ export async function createApp() {
const adapter = new SocketIoAdapter(app); const adapter = new SocketIoAdapter(app);
app.useWebSocketAdapter(adapter); app.useWebSocketAdapter(adapter);
return app; 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}`);
} }

View File

@@ -12,9 +12,9 @@
}, },
"include": ["./src"], "include": ["./src"],
"references": [ "references": [
{ "path": "../native" },
{ "path": "../../../tools/cli" }, { "path": "../../../tools/cli" },
{ "path": "../../../tools/utils" }, { "path": "../../../tools/utils" },
{ "path": "../../common/graphql" }, { "path": "../../common/graphql" }
{ "path": "../native" }
] ]
} }

View File

@@ -34,7 +34,6 @@
"devDependencies": { "devDependencies": {
"@affine/templates": "workspace:*", "@affine/templates": "workspace:*",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@swc/core": "^1.10.1",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.1.0", "@testing-library/react": "^16.1.0",
"@types/react": "^19.0.1", "@types/react": "^19.0.1",

View File

@@ -84,7 +84,6 @@
"zod": "^3.24.1" "zod": "^3.24.1"
}, },
"devDependencies": { "devDependencies": {
"@swc/core": "^1.10.1",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.1.0", "@testing-library/react": "^16.1.0",
"@types/animejs": "^3.1.12", "@types/animejs": "^3.1.12",

View File

@@ -21,6 +21,7 @@
"@napi-rs/simple-git": "^0.1.19", "@napi-rs/simple-git": "^0.1.19",
"@perfsee/webpack": "^1.13.0", "@perfsee/webpack": "^1.13.0",
"@sentry/webpack-plugin": "^3.0.0", "@sentry/webpack-plugin": "^3.0.0",
"@swc/core": "^1.10.1",
"@tailwindcss/postcss": "^4.0.0", "@tailwindcss/postcss": "^4.0.0",
"@vanilla-extract/webpack-plugin": "^2.3.15", "@vanilla-extract/webpack-plugin": "^2.3.15",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
@@ -34,6 +35,7 @@
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mime-types": "^3.0.0", "mime-types": "^3.0.0",
"mini-css-extract-plugin": "^2.9.2", "mini-css-extract-plugin": "^2.9.2",
"node-loader": "^2.1.0",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
"prettier": "^3.4.2", "prettier": "^3.4.2",

View File

@@ -10,12 +10,16 @@ import WebpackDevServer, {
} from 'webpack-dev-server'; } from 'webpack-dev-server';
import { Option, PackageCommand } from './command'; import { Option, PackageCommand } from './command';
import { createHTMLTargetConfig, createWorkerTargetConfig } from './webpack'; import {
createHTMLTargetConfig,
createNodeTargetConfig,
createWorkerTargetConfig,
} from './webpack';
function getBundleConfigs(pkg: Package) { function getBaseWorkerConfigs(pkg: Package) {
const core = new Package('@affine/core'); const core = new Package('@affine/core');
const workerConfigs = [ return [
createWorkerTargetConfig( createWorkerTargetConfig(
pkg, pkg,
core.srcPath.join( core.srcPath.join(
@@ -31,7 +35,9 @@ function getBundleConfigs(pkg: Package) {
core.srcPath.join('blocksuite/extensions/turbo-painter.worker.ts').value core.srcPath.join('blocksuite/extensions/turbo-painter.worker.ts').value
), ),
]; ];
}
function getBundleConfigs(pkg: Package) {
switch (pkg.name) { switch (pkg.name) {
case '@affine/admin': { case '@affine/admin': {
return [createHTMLTargetConfig(pkg, pkg.srcPath.join('index.tsx').value)]; return [createHTMLTargetConfig(pkg, pkg.srcPath.join('index.tsx').value)];
@@ -40,6 +46,7 @@ function getBundleConfigs(pkg: Package) {
case '@affine/mobile': case '@affine/mobile':
case '@affine/ios': case '@affine/ios':
case '@affine/android': { case '@affine/android': {
const workerConfigs = getBaseWorkerConfigs(pkg);
workerConfigs.push( workerConfigs.push(
createWorkerTargetConfig( createWorkerTargetConfig(
pkg, pkg,
@@ -58,6 +65,8 @@ function getBundleConfigs(pkg: Package) {
]; ];
} }
case '@affine/electron-renderer': { case '@affine/electron-renderer': {
const workerConfigs = getBaseWorkerConfigs(pkg);
return [ return [
createHTMLTargetConfig( createHTMLTargetConfig(
pkg, pkg,
@@ -78,10 +87,14 @@ function getBundleConfigs(pkg: Package) {
...workerConfigs, ...workerConfigs,
]; ];
} }
case '@affine/server': {
return [createNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value)];
}
} }
throw new Error(`Unsupported package: ${pkg.name}`); throw new Error(`Unsupported package: ${pkg.name}`);
} }
const IN_CI = !!process.env.CI; const IN_CI = !!process.env.CI;
const httpProxyMiddlewareLogLevel = IN_CI ? 'silent' : 'error'; const httpProxyMiddlewareLogLevel = IN_CI ? 'silent' : 'error';

View File

@@ -2,7 +2,7 @@ import { createRequire } from 'node:module';
import path from 'node:path'; import path from 'node:path';
import { getBuildConfig } from '@affine-tools/utils/build-config'; import { getBuildConfig } from '@affine-tools/utils/build-config';
import { ProjectRoot } from '@affine-tools/utils/path'; import { Path, ProjectRoot } from '@affine-tools/utils/path';
import { Package } from '@affine-tools/utils/workspace'; import { Package } from '@affine-tools/utils/workspace';
import { PerfseePlugin } from '@perfsee/webpack'; import { PerfseePlugin } from '@perfsee/webpack';
import { sentryWebpackPlugin } from '@sentry/webpack-plugin'; import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
@@ -75,10 +75,7 @@ export function createHTMLTargetConfig(
}, },
entry, entry,
output: { output: {
environment: { environment: { module: true, dynamicImport: true },
module: true,
dynamicImport: true,
},
filename: buildConfig.debug filename: buildConfig.debug
? 'js/[name].js' ? 'js/[name].js'
: 'js/[name].[contenthash:8].js', : 'js/[name].[contenthash:8].js',
@@ -127,12 +124,7 @@ export function createHTMLTargetConfig(
}, },
//#region rules //#region rules
rules: [ rules: [
{ { test: /\.m?js?$/, resolve: { fullySpecified: false } },
test: /\.m?js?$/,
resolve: {
fullySpecified: false,
},
},
{ {
test: /\.js$/, test: /\.js$/,
enforce: 'pre', enforce: 'pre',
@@ -185,9 +177,7 @@ export function createHTMLTargetConfig(
target: 'es2022', target: 'es2022',
externalHelpers: false, externalHelpers: false,
transform: { transform: {
react: { react: { runtime: 'automatic' },
runtime: 'automatic',
},
useDefineForClassFields: false, useDefineForClassFields: false,
decoratorVersion: '2022-03', decoratorVersion: '2022-03',
}, },
@@ -200,18 +190,9 @@ export function createHTMLTargetConfig(
test: /\.(png|jpg|gif|svg|webp|mp4|zip)$/, test: /\.(png|jpg|gif|svg|webp|mp4|zip)$/,
type: 'asset/resource', type: 'asset/resource',
}, },
{ { test: /\.(ttf|eot|woff|woff2)$/, type: 'asset/resource' },
test: /\.(ttf|eot|woff|woff2)$/, { test: /\.txt$/, type: 'asset/source' },
type: 'asset/resource', { test: /\.inline\.svg$/, type: 'asset/inline' },
},
{
test: /\.txt$/,
type: 'asset/source',
},
{
test: /\.inline\.svg$/,
type: 'asset/inline',
},
{ {
test: /\.css$/, test: /\.css$/,
use: [ use: [
@@ -242,12 +223,7 @@ export function createHTMLTargetConfig(
] ]
: [ : [
cssnano({ cssnano({
preset: [ preset: ['default', { convertValues: false }],
'default',
{
convertValues: false,
},
],
}), }),
], ],
}, },
@@ -298,9 +274,7 @@ export function createHTMLTargetConfig(
new WebpackS3Plugin(), new WebpackS3Plugin(),
!buildConfig.debug && !buildConfig.debug &&
process.env.PERFSEE_TOKEN && process.env.PERFSEE_TOKEN &&
new PerfseePlugin({ new PerfseePlugin({ project: 'affine-toeverything' }),
project: 'affine-toeverything',
}),
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG && process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT && process.env.SENTRY_PROJECT &&
@@ -325,9 +299,7 @@ export function createHTMLTargetConfig(
]), ]),
//#endregion //#endregion
stats: { stats: { errorDetails: true },
errorDetails: true,
},
//#region optimization //#region optimization
optimization: { optimization: {
@@ -339,12 +311,8 @@ export function createHTMLTargetConfig(
extractComments: true, extractComments: true,
terserOptions: { terserOptions: {
ecma: 2020, ecma: 2020,
compress: { compress: { unused: true },
unused: true, mangle: { keep_classnames: true },
},
mangle: {
keep_classnames: true,
},
}, },
}), }),
], ],
@@ -353,9 +321,7 @@ export function createHTMLTargetConfig(
usedExports: true, usedExports: true,
sideEffects: true, sideEffects: true,
removeAvailableModules: true, removeAvailableModules: true,
runtimeChunk: { runtimeChunk: { name: 'runtime' },
name: 'runtime',
},
splitChunks: { splitChunks: {
chunks: 'all', chunks: 'all',
minSize: 1, minSize: 1,
@@ -382,11 +348,7 @@ export function createHTMLTargetConfig(
priority: -10, priority: -10,
reuseExistingChunk: true, reuseExistingChunk: true,
}, },
default: { default: { minChunks: 2, priority: -20, reuseExistingChunk: true },
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
styles: { styles: {
name: 'styles', name: 'styles',
type: 'css/mini-extract', type: 'css/mini-extract',
@@ -416,9 +378,7 @@ export function createWorkerTargetConfig(
outputModule: false, outputModule: false,
syncWebAssembly: true, syncWebAssembly: true,
}, },
entry: { entry: { [workerName]: entry },
[workerName]: entry,
},
output: { output: {
filename: `js/${workerName}-${buildConfig.appVersion}.worker.js`, filename: `js/${workerName}-${buildConfig.appVersion}.worker.js`,
path: pkg.distPath.value, path: pkg.distPath.value,
@@ -432,14 +392,9 @@ export function createWorkerTargetConfig(
devtool: buildConfig.debug ? 'cheap-module-source-map' : 'source-map', devtool: buildConfig.debug ? 'cheap-module-source-map' : 'source-map',
resolve: { resolve: {
symlinks: true, symlinks: true,
extensionAlias: { extensionAlias: { '.js': ['.js', '.ts'], '.mjs': ['.mjs', '.mts'] },
'.js': ['.js', '.ts'],
'.mjs': ['.mjs', '.mts'],
},
extensions: ['.js', '.ts'], extensions: ['.js', '.ts'],
alias: { alias: { yjs: ProjectRoot.join('node_modules', 'yjs').value },
yjs: ProjectRoot.join('node_modules', 'yjs').value,
},
}, },
module: { module: {
@@ -454,12 +409,7 @@ export function createWorkerTargetConfig(
}, },
}, },
rules: [ rules: [
{ { test: /\.m?js?$/, resolve: { fullySpecified: false } },
test: /\.m?js?$/,
resolve: {
fullySpecified: false,
},
},
{ {
test: /\.js$/, test: /\.js$/,
enforce: 'pre', enforce: 'pre',
@@ -508,9 +458,7 @@ export function createWorkerTargetConfig(
{} as Record<string, string> {} as Record<string, string>
) )
), ),
new webpack.optimize.LimitChunkCountPlugin({ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
maxChunks: 1,
}),
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG && process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT && process.env.SENTRY_PROJECT &&
@@ -520,9 +468,7 @@ export function createWorkerTargetConfig(
authToken: process.env.SENTRY_AUTH_TOKEN, authToken: process.env.SENTRY_AUTH_TOKEN,
}), }),
]), ]),
stats: { stats: { errorDetails: true },
errorDetails: true,
},
optimization: { optimization: {
minimize: !buildConfig.debug, minimize: !buildConfig.debug,
minimizer: [ minimizer: [
@@ -532,12 +478,8 @@ export function createWorkerTargetConfig(
extractComments: true, extractComments: true,
terserOptions: { terserOptions: {
ecma: 2020, ecma: 2020,
compress: { compress: { unused: true },
unused: true, mangle: { keep_classnames: true },
},
mangle: {
keep_classnames: true,
},
}, },
}), }),
], ],
@@ -549,8 +491,120 @@ export function createWorkerTargetConfig(
runtimeChunk: false, runtimeChunk: false,
splitChunks: false, splitChunks: false,
}, },
performance: { performance: { hints: false },
hints: false, };
}, }
export function createNodeTargetConfig(
pkg: Package,
entry: string
): Omit<webpack.Configuration, 'name'> & { 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$)/],
}; };
} }

View File

@@ -0,0 +1,18 @@
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
`;
}

View File

@@ -8,13 +8,12 @@ export interface YarnWorkspaceItem {
export interface CommonPackageJsonContent { export interface CommonPackageJsonContent {
name: string; name: string;
type?: 'module' | 'commonjs';
version: string; version: string;
private?: boolean; private?: boolean;
dependencies?: { [key: string]: string }; dependencies?: { [key: string]: string };
devDependencies?: { [key: string]: string }; devDependencies?: { [key: string]: string };
scripts?: { [key: string]: string }; scripts?: { [key: string]: string };
main?: string; main?: string;
exports?: { exports?: { [key: string]: string | { [key: string]: string } };
[key: string]: string;
};
} }

View File

@@ -963,10 +963,10 @@ export const PackageList = [
location: 'packages/backend/server', location: 'packages/backend/server',
name: '@affine/server', name: '@affine/server',
workspaceDependencies: [ workspaceDependencies: [
'packages/backend/native',
'tools/cli', 'tools/cli',
'tools/utils', 'tools/utils',
'packages/common/graphql', 'packages/common/graphql',
'packages/backend/native',
], ],
}, },
{ {

View File

@@ -122,6 +122,7 @@ __metadata:
"@napi-rs/simple-git": "npm:^0.1.19" "@napi-rs/simple-git": "npm:^0.1.19"
"@perfsee/webpack": "npm:^1.13.0" "@perfsee/webpack": "npm:^1.13.0"
"@sentry/webpack-plugin": "npm:^3.0.0" "@sentry/webpack-plugin": "npm:^3.0.0"
"@swc/core": "npm:^1.10.1"
"@tailwindcss/postcss": "npm:^4.0.0" "@tailwindcss/postcss": "npm:^4.0.0"
"@types/lodash-es": "npm:^4.17.12" "@types/lodash-es": "npm:^4.17.12"
"@types/mime-types": "npm:^2.1.4" "@types/mime-types": "npm:^2.1.4"
@@ -139,6 +140,7 @@ __metadata:
lodash-es: "npm:^4.17.21" lodash-es: "npm:^4.17.21"
mime-types: "npm:^3.0.0" mime-types: "npm:^3.0.0"
mini-css-extract-plugin: "npm:^2.9.2" mini-css-extract-plugin: "npm:^2.9.2"
node-loader: "npm:^2.1.0"
postcss: "npm:^8.4.49" postcss: "npm:^8.4.49"
postcss-loader: "npm:^8.1.1" postcss-loader: "npm:^8.1.1"
prettier: "npm:^3.4.2" prettier: "npm:^3.4.2"
@@ -416,7 +418,6 @@ __metadata:
"@radix-ui/react-slot": "npm:^1.1.1" "@radix-ui/react-slot": "npm:^1.1.1"
"@radix-ui/react-toolbar": "npm:^1.1.1" "@radix-ui/react-toolbar": "npm:^1.1.1"
"@sentry/react": "npm:^9.2.0" "@sentry/react": "npm:^9.2.0"
"@swc/core": "npm:^1.10.1"
"@testing-library/dom": "npm:^10.4.0" "@testing-library/dom": "npm:^10.4.0"
"@testing-library/react": "npm:^16.1.0" "@testing-library/react": "npm:^16.1.0"
"@toeverything/infra": "workspace:*" "@toeverything/infra": "workspace:*"
@@ -14286,7 +14287,6 @@ __metadata:
"@affine/templates": "workspace:*" "@affine/templates": "workspace:*"
"@emotion/react": "npm:^11.14.0" "@emotion/react": "npm:^11.14.0"
"@preact/signals-core": "npm:^1.8.0" "@preact/signals-core": "npm:^1.8.0"
"@swc/core": "npm:^1.10.1"
"@testing-library/dom": "npm:^10.4.0" "@testing-library/dom": "npm:^10.4.0"
"@testing-library/react": "npm:^16.1.0" "@testing-library/react": "npm:^16.1.0"
"@types/react": "npm:^19.0.1" "@types/react": "npm:^19.0.1"
@@ -25145,7 +25145,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"loader-utils@npm:^2.0.0": "loader-utils@npm:^2.0.0, loader-utils@npm:^2.0.3":
version: 2.0.4 version: 2.0.4
resolution: "loader-utils@npm:2.0.4" resolution: "loader-utils@npm:2.0.4"
dependencies: dependencies:
@@ -27407,6 +27407,17 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "node-releases@npm:^2.0.19":
version: 2.0.19 version: 2.0.19
resolution: "node-releases@npm:2.0.19" resolution: "node-releases@npm:2.0.19"