mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-11 15:16:28 +08:00
2414aa5848
#### PR Dependency Tree * **PR #14485** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Admin static assets now served under /admin for self-hosted installs * CLI is directly executable from the command line * Build tooling supports a configurable self-hosted public path * Updated admin package script for adding UI components * Added a PostCSS dependency and plugin to the build toolchain for admin builds * **Style** * Switched queue module to a local queuedash stylesheet, added queuedash Tailwind layer, and scoped queuedash styles for the admin UI * **Bug Fixes** * Improved error propagation in the Electron renderer * Migration compatibility to repair a legacy checksum during native storage upgrades * **Tests** * Added tests covering the migration repair flow <!-- end of auto-generated comment: release notes by coderabbit.ai -->
434 lines
12 KiB
TypeScript
434 lines
12 KiB
TypeScript
import { rmSync } from 'node:fs';
|
|
import { cpus } from 'node:os';
|
|
|
|
import { Logger } from '@affine-tools/utils/logger';
|
|
import { Package } from '@affine-tools/utils/workspace';
|
|
import rspack, { type MultiRspackOptions } from '@rspack/core';
|
|
import {
|
|
type Configuration as RspackDevServerConfiguration,
|
|
RspackDevServer,
|
|
} from '@rspack/dev-server';
|
|
import { merge } from 'lodash-es';
|
|
import webpack from 'webpack';
|
|
import WebpackDevServer, {
|
|
type Configuration as WebpackDevServerConfiguration,
|
|
} from 'webpack-dev-server';
|
|
|
|
import {
|
|
assertRspackSupportedPackageName,
|
|
DEFAULT_DEV_SERVER_CONFIG,
|
|
isRspackSupportedPackageName,
|
|
} from './bundle-shared';
|
|
import { type Bundler, getBundler } from './bundler';
|
|
import { Option, PackageCommand } from './command';
|
|
import {
|
|
createHTMLTargetConfig as createRspackHTMLTargetConfig,
|
|
createNodeTargetConfig as createRspackNodeTargetConfig,
|
|
createWorkerTargetConfig as createRspackWorkerTargetConfig,
|
|
} from './rspack';
|
|
import {
|
|
createHTMLTargetConfig as createWebpackHTMLTargetConfig,
|
|
createNodeTargetConfig as createWebpackNodeTargetConfig,
|
|
createWorkerTargetConfig as createWebpackWorkerTargetConfig,
|
|
} from './webpack';
|
|
import {
|
|
shouldUploadReleaseAssets,
|
|
uploadDistAssetsToS3,
|
|
} from './webpack/s3-plugin.js';
|
|
|
|
type WorkerConfig = { name: string };
|
|
type CreateWorkerTargetConfig = (pkg: Package, entry: string) => WorkerConfig;
|
|
|
|
function assertRspackSupportedPackage(pkg: Package) {
|
|
assertRspackSupportedPackageName(pkg.name);
|
|
}
|
|
|
|
function shouldUploadAssetsForPackage(pkg: Package): boolean {
|
|
return (
|
|
!!process.env.R2_SECRET_ACCESS_KEY && shouldUploadReleaseAssets(pkg.name)
|
|
);
|
|
}
|
|
|
|
async function uploadAssetsForPackage(pkg: Package, logger: Logger) {
|
|
if (!shouldUploadAssetsForPackage(pkg)) {
|
|
return;
|
|
}
|
|
logger.info('Uploading dist assets to R2...');
|
|
await uploadDistAssetsToS3(pkg.distPath.value);
|
|
logger.info('Uploaded dist assets to R2.');
|
|
}
|
|
|
|
function getBaseWorkerConfigs(
|
|
pkg: Package,
|
|
createWorkerTargetConfig: CreateWorkerTargetConfig
|
|
) {
|
|
const core = new Package('@affine/core');
|
|
|
|
return [
|
|
createWorkerTargetConfig(
|
|
pkg,
|
|
core.srcPath.join(
|
|
'modules/workspace-engine/impls/workspace-profile.worker.ts'
|
|
).value
|
|
),
|
|
createWorkerTargetConfig(
|
|
pkg,
|
|
core.srcPath.join('modules/pdf/renderer/pdf.worker.ts').value
|
|
),
|
|
createWorkerTargetConfig(
|
|
pkg,
|
|
core.srcPath.join(
|
|
'blocksuite/view-extensions/turbo-renderer/turbo-painter.worker.ts'
|
|
).value
|
|
),
|
|
];
|
|
}
|
|
|
|
function getWebpackBundleConfigs(pkg: Package): webpack.MultiConfiguration {
|
|
switch (pkg.name) {
|
|
case '@affine/admin': {
|
|
return [
|
|
createWebpackHTMLTargetConfig(
|
|
pkg,
|
|
pkg.srcPath.join('index.tsx').value,
|
|
{ selfhostPublicPath: '/admin/' }
|
|
),
|
|
] as webpack.MultiConfiguration;
|
|
}
|
|
case '@affine/web':
|
|
case '@affine/mobile':
|
|
case '@affine/ios':
|
|
case '@affine/android': {
|
|
const workerConfigs = getBaseWorkerConfigs(
|
|
pkg,
|
|
createWebpackWorkerTargetConfig
|
|
);
|
|
workerConfigs.push(
|
|
createWebpackWorkerTargetConfig(
|
|
pkg,
|
|
pkg.srcPath.join('nbstore.worker.ts').value
|
|
)
|
|
);
|
|
|
|
return [
|
|
createWebpackHTMLTargetConfig(
|
|
pkg,
|
|
pkg.srcPath.join('index.tsx').value,
|
|
{},
|
|
workerConfigs.map(config => config.name)
|
|
),
|
|
...workerConfigs,
|
|
] as webpack.MultiConfiguration;
|
|
}
|
|
case '@affine/electron-renderer': {
|
|
const workerConfigs = getBaseWorkerConfigs(
|
|
pkg,
|
|
createWebpackWorkerTargetConfig
|
|
);
|
|
|
|
return [
|
|
createWebpackHTMLTargetConfig(
|
|
pkg,
|
|
{
|
|
index: pkg.srcPath.join('app/index.tsx').value,
|
|
shell: pkg.srcPath.join('shell/index.tsx').value,
|
|
popup: pkg.srcPath.join('popup/index.tsx').value,
|
|
backgroundWorker: pkg.srcPath.join('background-worker/index.ts')
|
|
.value,
|
|
},
|
|
{
|
|
additionalEntryForSelfhost: false,
|
|
injectGlobalErrorHandler: false,
|
|
emitAssetsManifest: false,
|
|
},
|
|
workerConfigs.map(config => config.name)
|
|
),
|
|
...workerConfigs,
|
|
] as webpack.MultiConfiguration;
|
|
}
|
|
case '@affine/server': {
|
|
return [
|
|
createWebpackNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value),
|
|
] as webpack.MultiConfiguration;
|
|
}
|
|
}
|
|
|
|
throw new Error(`Unsupported package: ${pkg.name}`);
|
|
}
|
|
|
|
function getRspackBundleConfigs(pkg: Package): MultiRspackOptions {
|
|
assertRspackSupportedPackage(pkg);
|
|
|
|
switch (pkg.name) {
|
|
case '@affine/admin': {
|
|
return [
|
|
createRspackHTMLTargetConfig(pkg, pkg.srcPath.join('index.tsx').value, {
|
|
selfhostPublicPath: '/admin/',
|
|
}),
|
|
] as MultiRspackOptions;
|
|
}
|
|
case '@affine/web':
|
|
case '@affine/mobile':
|
|
case '@affine/ios':
|
|
case '@affine/android': {
|
|
const workerConfigs = getBaseWorkerConfigs(
|
|
pkg,
|
|
createRspackWorkerTargetConfig
|
|
);
|
|
workerConfigs.push(
|
|
createRspackWorkerTargetConfig(
|
|
pkg,
|
|
pkg.srcPath.join('nbstore.worker.ts').value
|
|
)
|
|
);
|
|
|
|
return [
|
|
createRspackHTMLTargetConfig(
|
|
pkg,
|
|
pkg.srcPath.join('index.tsx').value,
|
|
{},
|
|
workerConfigs.map(config => config.name)
|
|
),
|
|
...workerConfigs,
|
|
] as MultiRspackOptions;
|
|
}
|
|
case '@affine/electron-renderer': {
|
|
const workerConfigs = getBaseWorkerConfigs(
|
|
pkg,
|
|
createRspackWorkerTargetConfig
|
|
);
|
|
|
|
return [
|
|
createRspackHTMLTargetConfig(
|
|
pkg,
|
|
{
|
|
index: pkg.srcPath.join('app/index.tsx').value,
|
|
shell: pkg.srcPath.join('shell/index.tsx').value,
|
|
popup: pkg.srcPath.join('popup/index.tsx').value,
|
|
backgroundWorker: pkg.srcPath.join('background-worker/index.ts')
|
|
.value,
|
|
},
|
|
{
|
|
additionalEntryForSelfhost: false,
|
|
injectGlobalErrorHandler: false,
|
|
emitAssetsManifest: false,
|
|
},
|
|
workerConfigs.map(config => config.name)
|
|
),
|
|
...workerConfigs,
|
|
] as MultiRspackOptions;
|
|
}
|
|
case '@affine/server': {
|
|
return [
|
|
createRspackNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value),
|
|
] as MultiRspackOptions;
|
|
}
|
|
}
|
|
|
|
throw new Error(`Unsupported package: ${pkg.name}`);
|
|
}
|
|
|
|
export class BundleCommand extends PackageCommand {
|
|
static override paths = [['bundle'], ['webpack'], ['pack'], ['bun']];
|
|
|
|
// bundle is not able to run with deps
|
|
override _deps = false;
|
|
override waitDeps = false;
|
|
|
|
dev = Option.Boolean('--dev,-d', false, {
|
|
description: 'Run in Development mode',
|
|
});
|
|
|
|
async execute() {
|
|
const pkg = this.workspace.getPackage(this.package);
|
|
const bundler = getBundler();
|
|
|
|
if (this.dev) {
|
|
await BundleCommand.dev(pkg, bundler);
|
|
} else {
|
|
await BundleCommand.build(pkg, bundler);
|
|
}
|
|
}
|
|
|
|
static async build(pkg: Package, bundler: Bundler = getBundler()) {
|
|
if (bundler === 'rspack' && !isRspackSupportedPackageName(pkg.name)) {
|
|
return BundleCommand.buildWithWebpack(pkg);
|
|
}
|
|
|
|
switch (bundler) {
|
|
case 'webpack':
|
|
return BundleCommand.buildWithWebpack(pkg);
|
|
case 'rspack':
|
|
return BundleCommand.buildWithRspack(pkg);
|
|
}
|
|
}
|
|
|
|
static async buildWithWebpack(pkg: Package) {
|
|
process.env.NODE_ENV = 'production';
|
|
const logger = new Logger('bundle');
|
|
logger.info(`Packing package ${pkg.name} with webpack...`);
|
|
logger.info('Cleaning old output...');
|
|
rmSync(pkg.distPath.value, { recursive: true, force: true });
|
|
|
|
const config = getWebpackBundleConfigs(pkg);
|
|
config.parallelism = cpus().length;
|
|
|
|
const compiler = webpack(config);
|
|
if (!compiler) {
|
|
throw new Error('Failed to create webpack compiler');
|
|
}
|
|
|
|
try {
|
|
const stats = await new Promise<webpack.Stats | webpack.MultiStats>(
|
|
(resolve, reject) => {
|
|
compiler.run((error, stats) => {
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
if (!stats) {
|
|
reject(new Error('Failed to get webpack stats'));
|
|
return;
|
|
}
|
|
resolve(stats);
|
|
});
|
|
}
|
|
);
|
|
if (stats.hasErrors()) {
|
|
console.error(stats.toString('errors-only'));
|
|
process.exit(1);
|
|
return;
|
|
}
|
|
console.log(stats.toString('minimal'));
|
|
await uploadAssetsForPackage(pkg, logger);
|
|
} catch (error) {
|
|
console.error(error);
|
|
process.exit(1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static async dev(
|
|
pkg: Package,
|
|
bundler: Bundler = getBundler(),
|
|
devServerConfig?:
|
|
| WebpackDevServerConfiguration
|
|
| RspackDevServerConfiguration
|
|
) {
|
|
if (bundler === 'rspack' && !isRspackSupportedPackageName(pkg.name)) {
|
|
return BundleCommand.devWithWebpack(
|
|
pkg,
|
|
devServerConfig as WebpackDevServerConfiguration | undefined
|
|
);
|
|
}
|
|
|
|
switch (bundler) {
|
|
case 'webpack':
|
|
return BundleCommand.devWithWebpack(
|
|
pkg,
|
|
devServerConfig as WebpackDevServerConfiguration | undefined
|
|
);
|
|
case 'rspack':
|
|
return BundleCommand.devWithRspack(
|
|
pkg,
|
|
devServerConfig as RspackDevServerConfiguration | undefined
|
|
);
|
|
}
|
|
}
|
|
|
|
static async devWithWebpack(
|
|
pkg: Package,
|
|
devServerConfig?: WebpackDevServerConfiguration
|
|
) {
|
|
process.env.NODE_ENV = 'development';
|
|
const logger = new Logger('bundle');
|
|
logger.info(`Starting webpack dev server for ${pkg.name}...`);
|
|
|
|
const config = getWebpackBundleConfigs(pkg);
|
|
config.parallelism = cpus().length;
|
|
|
|
const compiler = webpack(config);
|
|
if (!compiler) {
|
|
throw new Error('Failed to create webpack compiler');
|
|
}
|
|
|
|
const devServer = new WebpackDevServer(
|
|
merge({}, DEFAULT_DEV_SERVER_CONFIG, devServerConfig),
|
|
compiler
|
|
);
|
|
|
|
await devServer.start();
|
|
}
|
|
|
|
static async buildWithRspack(pkg: Package) {
|
|
process.env.NODE_ENV = 'production';
|
|
assertRspackSupportedPackage(pkg);
|
|
|
|
const logger = new Logger('bundle');
|
|
logger.info(`Packing package ${pkg.name} with rspack...`);
|
|
logger.info('Cleaning old output...');
|
|
rmSync(pkg.distPath.value, { recursive: true, force: true });
|
|
|
|
const config = getRspackBundleConfigs(pkg);
|
|
config.parallelism = cpus().length;
|
|
|
|
const compiler = rspack(config);
|
|
if (!compiler) {
|
|
throw new Error('Failed to create rspack compiler');
|
|
}
|
|
|
|
try {
|
|
const stats = await new Promise<any>((resolve, reject) => {
|
|
compiler.run((error, stats) => {
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
if (!stats) {
|
|
reject(new Error('Failed to get rspack stats'));
|
|
return;
|
|
}
|
|
resolve(stats);
|
|
});
|
|
});
|
|
if (stats.hasErrors()) {
|
|
console.error(stats.toString('errors-only'));
|
|
process.exit(1);
|
|
return;
|
|
}
|
|
console.log(stats.toString('minimal'));
|
|
await uploadAssetsForPackage(pkg, logger);
|
|
} catch (error) {
|
|
console.error(error);
|
|
process.exit(1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static async devWithRspack(
|
|
pkg: Package,
|
|
devServerConfig?: RspackDevServerConfiguration
|
|
) {
|
|
process.env.NODE_ENV = 'development';
|
|
assertRspackSupportedPackage(pkg);
|
|
|
|
const logger = new Logger('bundle');
|
|
logger.info(`Starting rspack dev server for ${pkg.name}...`);
|
|
|
|
const config = getRspackBundleConfigs(pkg);
|
|
config.parallelism = cpus().length;
|
|
|
|
const compiler = rspack(config);
|
|
if (!compiler) {
|
|
throw new Error('Failed to create rspack compiler');
|
|
}
|
|
|
|
const devServer = new RspackDevServer(
|
|
merge({}, DEFAULT_DEV_SERVER_CONFIG, devServerConfig),
|
|
compiler
|
|
);
|
|
|
|
await devServer.start();
|
|
}
|
|
}
|