mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-03 18:40:00 +08:00
chore: add monorepo tools (#9196)
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import type { BUILD_CONFIG_TYPE } from '@affine/env/global';
|
||||
import type { Package } from '@affine-tools/utils/workspace';
|
||||
|
||||
import { PackageToDistribution } from './distribution';
|
||||
|
||||
export interface BuildFlags {
|
||||
channel: 'stable' | 'beta' | 'internal' | 'canary';
|
||||
mode: 'development' | 'production';
|
||||
}
|
||||
|
||||
export function getBuildConfig(
|
||||
pkg: Package,
|
||||
buildFlags: BuildFlags
|
||||
): BUILD_CONFIG_TYPE {
|
||||
const distribution = PackageToDistribution.get(pkg.name);
|
||||
|
||||
if (!distribution) {
|
||||
throw new Error(`Distribution for ${pkg.name} is not found`);
|
||||
}
|
||||
|
||||
const buildPreset: Record<BuildFlags['channel'], BUILD_CONFIG_TYPE> = {
|
||||
get stable() {
|
||||
return {
|
||||
debug: buildFlags.mode === 'development',
|
||||
distribution,
|
||||
isDesktopEdition: (
|
||||
['web', 'desktop', 'admin'] as BUILD_CONFIG_TYPE['distribution'][]
|
||||
).includes(distribution),
|
||||
isMobileEdition: (
|
||||
['mobile', 'ios', 'android'] as BUILD_CONFIG_TYPE['distribution'][]
|
||||
).includes(distribution),
|
||||
isElectron: distribution === 'desktop',
|
||||
isWeb: distribution === 'web',
|
||||
isMobileWeb: distribution === 'mobile',
|
||||
isIOS: distribution === 'ios',
|
||||
isAndroid: distribution === 'android',
|
||||
isAdmin: distribution === 'admin',
|
||||
|
||||
appBuildType: 'stable' as const,
|
||||
serverUrlPrefix: 'https://app.affine.pro',
|
||||
appVersion: pkg.version,
|
||||
editorVersion: pkg.dependencies['@blocksuite/affine'],
|
||||
githubUrl: 'https://github.com/toeverything/AFFiNE',
|
||||
changelogUrl: 'https://affine.pro/what-is-new',
|
||||
downloadUrl: 'https://affine.pro/download',
|
||||
imageProxyUrl: '/api/worker/image-proxy',
|
||||
linkPreviewUrl: '/api/worker/link-preview',
|
||||
};
|
||||
},
|
||||
get beta() {
|
||||
return {
|
||||
...this.stable,
|
||||
appBuildType: 'beta' as const,
|
||||
serverUrlPrefix: 'https://insider.affine.pro',
|
||||
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
|
||||
};
|
||||
},
|
||||
get internal() {
|
||||
return {
|
||||
...this.stable,
|
||||
appBuildType: 'internal' as const,
|
||||
serverUrlPrefix: 'https://insider.affine.pro',
|
||||
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
|
||||
};
|
||||
},
|
||||
// canary will be aggressive and enable all features
|
||||
get canary() {
|
||||
return {
|
||||
...this.stable,
|
||||
appBuildType: 'canary' as const,
|
||||
serverUrlPrefix: 'https://affine.fail',
|
||||
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const currentBuild = buildFlags.channel;
|
||||
|
||||
if (!(currentBuild in buildPreset)) {
|
||||
throw new Error(`BUILD_TYPE ${currentBuild} is not supported`);
|
||||
}
|
||||
|
||||
const currentBuildPreset = buildPreset[currentBuild];
|
||||
|
||||
const environmentPreset = {
|
||||
changelogUrl: process.env.CHANGELOG_URL ?? currentBuildPreset.changelogUrl,
|
||||
};
|
||||
|
||||
if (buildFlags.mode === 'development') {
|
||||
currentBuildPreset.serverUrlPrefix = 'http://localhost:8080';
|
||||
}
|
||||
|
||||
return {
|
||||
...currentBuildPreset,
|
||||
// environment preset will overwrite current build preset
|
||||
// this environment variable is for debug proposes only
|
||||
// do not put them into CI
|
||||
...(process.env.CI ? {} : environmentPreset),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { BUILD_CONFIG_TYPE } from '@affine/env/global';
|
||||
|
||||
import { PackageList, type PackageName } from './workspace.gen';
|
||||
|
||||
export const PackageToDistribution = new Map<
|
||||
PackageName,
|
||||
BUILD_CONFIG_TYPE['distribution']
|
||||
>([
|
||||
['@affine/admin', 'admin'],
|
||||
['@affine/web', 'web'],
|
||||
['@affine/electron', 'desktop'],
|
||||
['@affine/mobile', 'mobile'],
|
||||
['@affine/ios', 'ios'],
|
||||
['@affine/android', 'android'],
|
||||
]);
|
||||
|
||||
export const AliasToPackage = new Map<string, PackageName>([
|
||||
['admin', '@affine/admin'],
|
||||
['web', '@affine/web'],
|
||||
['desktop', '@affine/electron'],
|
||||
['electron', '@affine/electron'],
|
||||
['mobile', '@affine/mobile'],
|
||||
['ios', '@affine/ios'],
|
||||
['android', '@affine/android'],
|
||||
['server', '@affine/server'],
|
||||
['gql', '@affine/graphql'],
|
||||
...PackageList.map(
|
||||
pkg => [pkg.name.split('/').pop()!, pkg.name] as [string, PackageName]
|
||||
),
|
||||
]);
|
||||
@@ -0,0 +1,46 @@
|
||||
import chalk from 'chalk';
|
||||
import identity from 'lodash-es/identity';
|
||||
|
||||
export const newLineSeparator = /\r\n|[\n\r\x85\u2028\u2029]/g;
|
||||
|
||||
interface StringLike {
|
||||
toString: () => string;
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
log = this.getLineLogger(console.log.bind(console));
|
||||
info = this.getLineLogger(console.info.bind(console), chalk.blue);
|
||||
warn = this.getLineLogger(
|
||||
console.warn.bind(console),
|
||||
chalk.bgHex('#322b08').hex('#fadea6')
|
||||
);
|
||||
error = this.getLineLogger(
|
||||
console.error.bind(console),
|
||||
chalk.bgHex('#250201').hex('#ef8784')
|
||||
);
|
||||
success = this.getLineLogger(console.log.bind(console), chalk.green);
|
||||
|
||||
constructor(private readonly tag: string = '') {}
|
||||
|
||||
getLineLogger(
|
||||
logLine: (...line: string[]) => void,
|
||||
color: (...text: string[]) => string = identity
|
||||
) {
|
||||
return (...args: StringLike[]) => {
|
||||
args.forEach(arg => {
|
||||
arg
|
||||
.toString()
|
||||
.split(newLineSeparator)
|
||||
.forEach(line => {
|
||||
if (line.length !== 0) {
|
||||
if (this.tag) {
|
||||
logLine(color(`[${this.tag}] ${line}`));
|
||||
} else {
|
||||
logLine(color(line));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { parse } from 'node:path';
|
||||
|
||||
import { type Path, ProjectRoot } from './path';
|
||||
import type { CommonPackageJsonContent, YarnWorkspaceItem } from './types';
|
||||
import type { Workspace } from './workspace';
|
||||
import { PackageList, type PackageName } from './workspace.gen';
|
||||
|
||||
export function readPackageJson(path: Path): CommonPackageJsonContent {
|
||||
const content = readFileSync(path.join('package.json').toString(), 'utf-8');
|
||||
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
export class Package {
|
||||
readonly name: PackageName;
|
||||
readonly packageJson: CommonPackageJsonContent;
|
||||
readonly dirname: string;
|
||||
readonly path: Path;
|
||||
readonly srcPath: Path;
|
||||
readonly nodeModulesPath: Path;
|
||||
readonly libPath: Path;
|
||||
readonly distPath: Path;
|
||||
readonly version: string;
|
||||
readonly isTsProject: boolean;
|
||||
readonly workspaceDependencies: string[];
|
||||
readonly deps: Package[] = [];
|
||||
private _workspace: Workspace | null = null;
|
||||
|
||||
get entry() {
|
||||
return this.packageJson.main || this.packageJson.exports?.['.'];
|
||||
}
|
||||
|
||||
get dependencies() {
|
||||
return this.packageJson.dependencies || {};
|
||||
}
|
||||
|
||||
get devDependencies() {
|
||||
return this.packageJson.devDependencies || {};
|
||||
}
|
||||
|
||||
get workspace() {
|
||||
if (!this._workspace) {
|
||||
throw new Error('Workspace is not initialized');
|
||||
}
|
||||
|
||||
return this._workspace;
|
||||
}
|
||||
|
||||
private set workspace(workspace: Workspace) {
|
||||
this._workspace = workspace;
|
||||
}
|
||||
|
||||
constructor(name: PackageName, meta?: YarnWorkspaceItem) {
|
||||
this.name = name as PackageName;
|
||||
meta ??= PackageList.find(item => item.name === name)!;
|
||||
|
||||
// parse paths
|
||||
this.path = ProjectRoot.join(meta.location);
|
||||
this.dirname = parse(meta.location).name;
|
||||
this.srcPath = this.path.join('src');
|
||||
this.libPath = this.path.join('lib');
|
||||
this.distPath = this.path.join('dist');
|
||||
this.nodeModulesPath = this.path.join('node_modules');
|
||||
|
||||
// parse workspace
|
||||
const packageJson = readPackageJson(this.path);
|
||||
this.packageJson = packageJson;
|
||||
this.version = packageJson.version;
|
||||
this.workspaceDependencies = meta.workspaceDependencies;
|
||||
this.isTsProject = this.path.join('tsconfig.json').isFile();
|
||||
}
|
||||
|
||||
get scripts() {
|
||||
return this.packageJson.scripts || {};
|
||||
}
|
||||
|
||||
join(...paths: string[]) {
|
||||
return this.path.join(...paths);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { existsSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
export class Path {
|
||||
static dir(url: string) {
|
||||
return new Path(join(fileURLToPath(url), '..'));
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
get relativePath() {
|
||||
return './' + this.path.slice(ProjectRoot.path.length).replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
constructor(public readonly path: string) {}
|
||||
|
||||
join(...paths: string[]) {
|
||||
return new Path(join(this.path, ...paths));
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
exists() {
|
||||
return existsSync(this.path);
|
||||
}
|
||||
|
||||
stats() {
|
||||
return statSync(this.path);
|
||||
}
|
||||
|
||||
isFile() {
|
||||
return this.exists() && this.stats().isFile();
|
||||
}
|
||||
|
||||
isDirectory() {
|
||||
return this.exists() && this.stats().isDirectory();
|
||||
}
|
||||
|
||||
toFileUrl() {
|
||||
return pathToFileURL(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
export const ProjectRoot = Path.dir(import.meta.url).join('../../../');
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
type ChildProcess,
|
||||
execSync,
|
||||
spawn as RawSpawn,
|
||||
type SpawnOptions,
|
||||
} from 'node:child_process';
|
||||
|
||||
import { Logger } from './logger';
|
||||
|
||||
const children = new Set<ChildProcess>();
|
||||
|
||||
export function spawn(
|
||||
tag: string,
|
||||
cmd: string | string[],
|
||||
options: SpawnOptions = {}
|
||||
) {
|
||||
cmd = typeof cmd === 'string' ? cmd.split(' ') : cmd;
|
||||
const isYarnSpawn = cmd[0] === 'yarn';
|
||||
|
||||
const spawnOptions: SpawnOptions = {
|
||||
stdio: isYarnSpawn
|
||||
? ['inherit', 'inherit', 'inherit']
|
||||
: ['inherit', 'pipe', 'pipe'],
|
||||
shell: true,
|
||||
...options,
|
||||
env: { ...process.env, ...options.env },
|
||||
};
|
||||
|
||||
const logger = new Logger(tag);
|
||||
logger.info(cmd.join(' '));
|
||||
const childProcess = RawSpawn(cmd[0], cmd.slice(1), spawnOptions);
|
||||
children.add(childProcess);
|
||||
|
||||
const drain = (_code: number | null, signal: any) => {
|
||||
children.delete(childProcess);
|
||||
|
||||
// don't run repeatedly if this is the error event
|
||||
if (signal === undefined) {
|
||||
childProcess.removeListener('exit', drain);
|
||||
}
|
||||
};
|
||||
|
||||
childProcess.stdout?.on('data', chunk => {
|
||||
logger.log(chunk);
|
||||
});
|
||||
|
||||
childProcess.stderr?.on('data', chunk => {
|
||||
logger.error(chunk);
|
||||
});
|
||||
|
||||
childProcess.once('error', e => {
|
||||
logger.error(e.toString());
|
||||
children.delete(childProcess);
|
||||
});
|
||||
|
||||
childProcess.once('exit', (code, signal) => {
|
||||
if (code !== 0) {
|
||||
logger.error('Finished with non-zero exit code.');
|
||||
}
|
||||
|
||||
drain(code, signal);
|
||||
});
|
||||
|
||||
return childProcess;
|
||||
}
|
||||
|
||||
export function execAsync(
|
||||
tag: string,
|
||||
cmd: string | string[],
|
||||
options?: SpawnOptions
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const childProcess = spawn(tag, cmd, options);
|
||||
|
||||
childProcess.once('error', e => {
|
||||
reject(e);
|
||||
});
|
||||
|
||||
childProcess.once('exit', code => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Child process exits with non-zero code ${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function exec(
|
||||
tag: string,
|
||||
cmd: string,
|
||||
{ silent }: { silent: boolean } = { silent: false }
|
||||
): string {
|
||||
const logger = new Logger(tag);
|
||||
!silent && logger.info(cmd);
|
||||
const result = execSync(cmd, { encoding: 'utf8' }).trim();
|
||||
!silent && logger.log(result);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export interface YarnWorkspaceItem {
|
||||
name: string;
|
||||
location: string;
|
||||
workspaceDependencies: string[];
|
||||
// we don't need it
|
||||
mismatchedWorkspaceDependencies?: string[];
|
||||
}
|
||||
|
||||
export interface CommonPackageJsonContent {
|
||||
name: string;
|
||||
version: string;
|
||||
private?: boolean;
|
||||
dependencies?: { [key: string]: string };
|
||||
devDependencies?: { [key: string]: string };
|
||||
scripts?: { [key: string]: string };
|
||||
main?: string;
|
||||
exports?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
// Auto generated content
|
||||
// DO NOT MODIFY THIS FILE MANUALLY
|
||||
export const PackageList = [
|
||||
{
|
||||
location: 'blocksuite/affine/all',
|
||||
name: '@blocksuite/affine',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/blocks',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/presets',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/affine/block-embed',
|
||||
name: '@blocksuite/affine-block-embed',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/block-surface',
|
||||
'blocksuite/affine/components',
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/affine/shared',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/affine/block-list',
|
||||
name: '@blocksuite/affine-block-list',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/components',
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/affine/shared',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/affine/block-paragraph',
|
||||
name: '@blocksuite/affine-block-paragraph',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/components',
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/affine/shared',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/affine/block-surface',
|
||||
name: '@blocksuite/affine-block-surface',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/components',
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/affine/shared',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/affine/components',
|
||||
name: '@blocksuite/affine-components',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/affine/shared',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/affine/data-view',
|
||||
name: '@blocksuite/data-view',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/components',
|
||||
'blocksuite/affine/shared',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/affine/model',
|
||||
name: '@blocksuite/affine-model',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/affine/shared',
|
||||
name: '@blocksuite/affine-shared',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/affine/widget-scroll-anchoring',
|
||||
name: '@blocksuite/affine-widget-scroll-anchoring',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/affine/shared',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/framework/global',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/blocks',
|
||||
name: '@blocksuite/blocks',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/block-embed',
|
||||
'blocksuite/affine/block-list',
|
||||
'blocksuite/affine/block-paragraph',
|
||||
'blocksuite/affine/block-surface',
|
||||
'blocksuite/affine/components',
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/affine/shared',
|
||||
'blocksuite/affine/widget-scroll-anchoring',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/affine/data-view',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/framework/block-std',
|
||||
name: '@blocksuite/block-std',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/framework/global',
|
||||
name: '@blocksuite/global',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/framework/inline',
|
||||
name: '@blocksuite/inline',
|
||||
workspaceDependencies: ['blocksuite/framework/global'],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/framework/store',
|
||||
name: '@blocksuite/store',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/sync',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/framework/sync',
|
||||
name: '@blocksuite/sync',
|
||||
workspaceDependencies: ['blocksuite/framework/global'],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/playground',
|
||||
name: '@blocksuite/playground',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/components',
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/blocks',
|
||||
'blocksuite/affine/data-view',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/presets',
|
||||
'blocksuite/framework/store',
|
||||
'blocksuite/framework/sync',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/presets',
|
||||
name: '@blocksuite/presets',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/block-surface',
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/affine/shared',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/blocks',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/framework/inline',
|
||||
'blocksuite/framework/store',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'blocksuite/tests-legacy',
|
||||
name: '@blocksuite/legacy-e2e',
|
||||
workspaceDependencies: [
|
||||
'blocksuite/affine/model',
|
||||
'blocksuite/framework/block-std',
|
||||
'blocksuite/framework/global',
|
||||
'blocksuite/presets',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'docs/reference',
|
||||
name: '@affine/docs',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'packages/backend/native',
|
||||
name: '@affine/server-native',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'packages/backend/server',
|
||||
name: '@affine/server',
|
||||
workspaceDependencies: ['tests/kit', 'packages/backend/native'],
|
||||
},
|
||||
{
|
||||
location: 'packages/common/debug',
|
||||
name: '@affine/debug',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'packages/common/env',
|
||||
name: '@affine/env',
|
||||
workspaceDependencies: ['blocksuite/affine/all'],
|
||||
},
|
||||
{
|
||||
location: 'packages/common/infra',
|
||||
name: '@toeverything/infra',
|
||||
workspaceDependencies: [
|
||||
'packages/common/debug',
|
||||
'packages/common/env',
|
||||
'packages/frontend/templates',
|
||||
'blocksuite/affine/all',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'packages/common/nbstore',
|
||||
name: '@affine/nbstore',
|
||||
workspaceDependencies: [
|
||||
'packages/common/infra',
|
||||
'packages/frontend/electron-api',
|
||||
'packages/frontend/graphql',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/admin',
|
||||
name: '@affine/admin',
|
||||
workspaceDependencies: [
|
||||
'packages/frontend/component',
|
||||
'packages/frontend/core',
|
||||
'packages/frontend/graphql',
|
||||
'packages/common/infra',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/apps/android',
|
||||
name: '@affine/android',
|
||||
workspaceDependencies: [
|
||||
'packages/frontend/component',
|
||||
'packages/frontend/core',
|
||||
'packages/frontend/i18n',
|
||||
'blocksuite/affine/all',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/apps/electron',
|
||||
name: '@affine/electron',
|
||||
workspaceDependencies: [
|
||||
'tests/kit',
|
||||
'tools/utils',
|
||||
'packages/frontend/component',
|
||||
'packages/frontend/core',
|
||||
'packages/frontend/electron-api',
|
||||
'packages/frontend/i18n',
|
||||
'packages/frontend/native',
|
||||
'packages/common/nbstore',
|
||||
'blocksuite/affine/all',
|
||||
'packages/common/infra',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/apps/ios',
|
||||
name: '@affine/ios',
|
||||
workspaceDependencies: [
|
||||
'packages/frontend/component',
|
||||
'packages/frontend/core',
|
||||
'packages/frontend/i18n',
|
||||
'blocksuite/affine/all',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/apps/mobile',
|
||||
name: '@affine/mobile',
|
||||
workspaceDependencies: [
|
||||
'packages/frontend/component',
|
||||
'packages/frontend/core',
|
||||
'packages/frontend/i18n',
|
||||
'blocksuite/affine/all',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/apps/web',
|
||||
name: '@affine/web',
|
||||
workspaceDependencies: [
|
||||
'packages/frontend/component',
|
||||
'packages/frontend/core',
|
||||
'packages/frontend/i18n',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/component',
|
||||
name: '@affine/component',
|
||||
workspaceDependencies: [
|
||||
'packages/common/debug',
|
||||
'packages/frontend/electron-api',
|
||||
'packages/frontend/graphql',
|
||||
'packages/frontend/i18n',
|
||||
'blocksuite/affine/all',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/core',
|
||||
name: '@affine/core',
|
||||
workspaceDependencies: [
|
||||
'packages/frontend/component',
|
||||
'packages/common/debug',
|
||||
'packages/frontend/electron-api',
|
||||
'packages/common/env',
|
||||
'packages/frontend/graphql',
|
||||
'packages/frontend/i18n',
|
||||
'packages/frontend/templates',
|
||||
'packages/frontend/track',
|
||||
'blocksuite/affine/all',
|
||||
],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/electron-api',
|
||||
name: '@affine/electron-api',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/graphql',
|
||||
name: '@affine/graphql',
|
||||
workspaceDependencies: ['packages/common/env'],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/i18n',
|
||||
name: '@affine/i18n',
|
||||
workspaceDependencies: ['packages/common/debug'],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/native',
|
||||
name: '@affine/native',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/templates',
|
||||
name: '@affine/templates',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'packages/frontend/track',
|
||||
name: '@affine/track',
|
||||
workspaceDependencies: ['packages/common/debug'],
|
||||
},
|
||||
{
|
||||
location: 'tests/affine-cloud',
|
||||
name: '@affine-test/affine-cloud',
|
||||
workspaceDependencies: ['tests/kit'],
|
||||
},
|
||||
{
|
||||
location: 'tests/affine-cloud-copilot',
|
||||
name: '@affine-test/affine-cloud-copilot',
|
||||
workspaceDependencies: ['tests/kit'],
|
||||
},
|
||||
{
|
||||
location: 'tests/affine-desktop',
|
||||
name: '@affine-test/affine-desktop',
|
||||
workspaceDependencies: ['tests/kit', 'packages/frontend/electron-api'],
|
||||
},
|
||||
{
|
||||
location: 'tests/affine-desktop-cloud',
|
||||
name: '@affine-test/affine-desktop-cloud',
|
||||
workspaceDependencies: ['tests/kit'],
|
||||
},
|
||||
{
|
||||
location: 'tests/affine-local',
|
||||
name: '@affine-test/affine-local',
|
||||
workspaceDependencies: ['tests/kit'],
|
||||
},
|
||||
{
|
||||
location: 'tests/affine-mobile',
|
||||
name: '@affine-test/affine-mobile',
|
||||
workspaceDependencies: ['tests/kit'],
|
||||
},
|
||||
{
|
||||
location: 'tests/kit',
|
||||
name: '@affine-test/kit',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'tools/@types/env',
|
||||
name: '@types/affine__env',
|
||||
workspaceDependencies: ['packages/common/env'],
|
||||
},
|
||||
{
|
||||
location: 'tools/changelog',
|
||||
name: '@affine/changelog',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'tools/cli',
|
||||
name: '@affine-tools/cli',
|
||||
workspaceDependencies: ['tools/utils'],
|
||||
},
|
||||
{
|
||||
location: 'tools/commitlint',
|
||||
name: '@affine/commitlint-config',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'tools/copilot-result',
|
||||
name: '@affine/copilot-result',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'tools/playstore-auto-bump',
|
||||
name: '@affine/playstore-auto-bump',
|
||||
workspaceDependencies: ['tools/utils'],
|
||||
},
|
||||
{
|
||||
location: 'tools/utils',
|
||||
name: '@affine-tools/utils',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
{
|
||||
location: 'tools/workers',
|
||||
name: '@affine/workers',
|
||||
workspaceDependencies: [],
|
||||
},
|
||||
];
|
||||
|
||||
export type PackageName =
|
||||
| '@blocksuite/affine'
|
||||
| '@blocksuite/affine-block-embed'
|
||||
| '@blocksuite/affine-block-list'
|
||||
| '@blocksuite/affine-block-paragraph'
|
||||
| '@blocksuite/affine-block-surface'
|
||||
| '@blocksuite/affine-components'
|
||||
| '@blocksuite/data-view'
|
||||
| '@blocksuite/affine-model'
|
||||
| '@blocksuite/affine-shared'
|
||||
| '@blocksuite/affine-widget-scroll-anchoring'
|
||||
| '@blocksuite/blocks'
|
||||
| '@blocksuite/block-std'
|
||||
| '@blocksuite/global'
|
||||
| '@blocksuite/inline'
|
||||
| '@blocksuite/store'
|
||||
| '@blocksuite/sync'
|
||||
| '@blocksuite/playground'
|
||||
| '@blocksuite/presets'
|
||||
| '@blocksuite/legacy-e2e'
|
||||
| '@affine/docs'
|
||||
| '@affine/server-native'
|
||||
| '@affine/server'
|
||||
| '@affine/debug'
|
||||
| '@affine/env'
|
||||
| '@toeverything/infra'
|
||||
| '@affine/nbstore'
|
||||
| '@affine/admin'
|
||||
| '@affine/android'
|
||||
| '@affine/electron'
|
||||
| '@affine/ios'
|
||||
| '@affine/mobile'
|
||||
| '@affine/web'
|
||||
| '@affine/component'
|
||||
| '@affine/core'
|
||||
| '@affine/electron-api'
|
||||
| '@affine/graphql'
|
||||
| '@affine/i18n'
|
||||
| '@affine/native'
|
||||
| '@affine/templates'
|
||||
| '@affine/track'
|
||||
| '@affine-test/affine-cloud'
|
||||
| '@affine-test/affine-cloud-copilot'
|
||||
| '@affine-test/affine-desktop'
|
||||
| '@affine-test/affine-desktop-cloud'
|
||||
| '@affine-test/affine-local'
|
||||
| '@affine-test/affine-mobile'
|
||||
| '@affine-test/kit'
|
||||
| '@types/affine__env'
|
||||
| '@affine/changelog'
|
||||
| '@affine-tools/cli'
|
||||
| '@affine/commitlint-config'
|
||||
| '@affine/copilot-result'
|
||||
| '@affine/playstore-auto-bump'
|
||||
| '@affine-tools/utils'
|
||||
| '@affine/workers';
|
||||
@@ -0,0 +1,204 @@
|
||||
import once from 'lodash-es/once';
|
||||
|
||||
import { Logger } from './logger';
|
||||
import { Package, readPackageJson } from './package';
|
||||
import { ProjectRoot } from './path';
|
||||
import { exec } from './process';
|
||||
import type { CommonPackageJsonContent, YarnWorkspaceItem } from './types';
|
||||
import { PackageList, type PackageName } from './workspace.gen';
|
||||
|
||||
class CircularDependenciesError extends Error {
|
||||
constructor(public currentName: string) {
|
||||
super('Circular dependencies error');
|
||||
}
|
||||
}
|
||||
|
||||
class ForbiddenPackageRefError extends Error {
|
||||
constructor(
|
||||
public currentName: string,
|
||||
public refName: string
|
||||
) {
|
||||
super(
|
||||
`Public package cannot reference private package. Found '${refName}' in dependencies of '${currentName}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Workspace {
|
||||
static PackageNames: PackageName[] = PackageList.map(
|
||||
p => p.name
|
||||
) as PackageName[];
|
||||
|
||||
readonly packages: Package[];
|
||||
|
||||
readonly packageJson: CommonPackageJsonContent;
|
||||
|
||||
private readonly logger = new Logger('AFFiNE');
|
||||
|
||||
readonly path = ProjectRoot;
|
||||
|
||||
get version() {
|
||||
return this.packageJson.version;
|
||||
}
|
||||
|
||||
get devDependencies() {
|
||||
return this.packageJson.devDependencies ?? {};
|
||||
}
|
||||
|
||||
get dependencies() {
|
||||
return this.packageJson.dependencies ?? {};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.packageJson = readPackageJson(ProjectRoot);
|
||||
const packages = new Map<string, Package>();
|
||||
|
||||
for (const meta of PackageList) {
|
||||
try {
|
||||
const pkg = new Package(meta.name as PackageName, meta);
|
||||
// @ts-expect-error internal api
|
||||
pkg.workspace = this;
|
||||
packages.set(meta.location, pkg);
|
||||
} catch (e) {
|
||||
this.logger.error(e as Error);
|
||||
}
|
||||
}
|
||||
|
||||
const building = new Set<string>();
|
||||
try {
|
||||
packages.forEach(pkg => this.buildDeps(pkg, packages, building));
|
||||
} catch (e) {
|
||||
if (e instanceof CircularDependenciesError) {
|
||||
const inProcessPackages = Array.from(building);
|
||||
const circle = inProcessPackages
|
||||
.slice(inProcessPackages.indexOf(e.currentName))
|
||||
.concat(e.currentName);
|
||||
this.logger.error(
|
||||
`Circular dependencies found: \n ${circle.join(' -> ')}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
this.packages = Array.from(packages.values());
|
||||
}
|
||||
|
||||
tryGetPackage(name: PackageName) {
|
||||
return this.packages.find(p => p.name === name);
|
||||
}
|
||||
|
||||
getPackage(name: PackageName) {
|
||||
const pkg = this.tryGetPackage(name);
|
||||
|
||||
if (!pkg) {
|
||||
throw new Error(`Cannot find package with name '${name}'`);
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
join(...paths: string[]) {
|
||||
return this.path.join(...paths);
|
||||
}
|
||||
|
||||
buildDeps(
|
||||
pkg: Package,
|
||||
packages: Map<string, Package>,
|
||||
building: Set<string>
|
||||
) {
|
||||
if (pkg.deps.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
building.add(pkg.name);
|
||||
|
||||
// @ts-expect-error workspace is the builder for package deps
|
||||
pkg.deps = pkg.workspaceDependencies
|
||||
.map(relativeDepPath => {
|
||||
const dep = packages.get(relativeDepPath);
|
||||
|
||||
if (!dep) {
|
||||
this.logger.error(
|
||||
`Cannot find package at ${relativeDepPath}. While build dependencies of ${pkg.name}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (building.has(dep.name)) {
|
||||
throw new CircularDependenciesError(pkg.name);
|
||||
}
|
||||
|
||||
if (!pkg.packageJson.private && dep.packageJson.private) {
|
||||
throw new ForbiddenPackageRefError(pkg.name, dep.name);
|
||||
}
|
||||
|
||||
this.buildDeps(dep, packages, building);
|
||||
return dep;
|
||||
})
|
||||
.filter(Boolean) as Package[];
|
||||
|
||||
building.delete(pkg.name);
|
||||
}
|
||||
|
||||
yarnList = once(() => {
|
||||
const output = exec('', 'yarn workspaces list -v --json', { silent: true });
|
||||
|
||||
let packageList = JSON.parse(
|
||||
`[${output.trim().replace(/\r\n|\n/g, ',')}]`
|
||||
) as YarnWorkspaceItem[];
|
||||
|
||||
packageList.forEach(p => {
|
||||
p.location = p.location.replaceAll(/\\/g, '/');
|
||||
delete p['mismatchedWorkspaceDependencies'];
|
||||
});
|
||||
|
||||
// ignore root package
|
||||
return packageList.filter(p => p.location !== '.');
|
||||
});
|
||||
|
||||
genWorkspaceInfo() {
|
||||
const list = this.yarnList();
|
||||
|
||||
const names = list.map(p => p.name);
|
||||
|
||||
const content = [
|
||||
'// Auto generated content',
|
||||
'// DO NOT MODIFY THIS FILE MANUALLY',
|
||||
`export const PackageList = ${JSON.stringify(list, null, 2)}`,
|
||||
'',
|
||||
`export type PackageName = ${names.map(n => `'${n}'`).join(' | ')}`,
|
||||
];
|
||||
|
||||
return content.join('\n');
|
||||
}
|
||||
|
||||
genProjectTsConfig() {
|
||||
const content = [
|
||||
'// Auto generated content',
|
||||
'// DO NOT MODIFY THIS FILE MANUALLY',
|
||||
'{',
|
||||
' "compilerOptions": {',
|
||||
' "noEmit": true',
|
||||
' },',
|
||||
' "include": [],',
|
||||
' "references": [',
|
||||
this.packages
|
||||
.filter(p => p.isTsProject)
|
||||
.map(p => ` { "path": "${p.path.relativePath}" }`)
|
||||
.join(',\n'),
|
||||
' ]',
|
||||
'}',
|
||||
'',
|
||||
];
|
||||
|
||||
return content.join('\n');
|
||||
}
|
||||
|
||||
forEach(callback: (pkg: Package) => void) {
|
||||
this.packages.forEach(callback);
|
||||
}
|
||||
}
|
||||
|
||||
export { Package, type PackageName };
|
||||
Reference in New Issue
Block a user