snapshot of https://github.com/toeverything/AFFiNE.git (canary, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-16 04:01:20 +05:00
commit 702cc698a7
9990 changed files with 1073618 additions and 0 deletions

23
tools/utils/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "@affine-tools/utils",
"version": "0.27.5",
"type": "module",
"private": true,
"exports": {
".": "./src/index.ts",
"./path": "./src/path.ts",
"./workspace": "./src/workspace.ts",
"./process": "./src/process.ts",
"./logger": "./src/logger.ts",
"./build-config": "./src/build-config.ts",
"./distribution": "./src/distribution.ts"
},
"dependencies": {
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.0.0",
"chalk": "^5.3.0",
"lodash-es": "^4.17.23",
"oxfmt": "0.63.0",
"typescript": "npm:@typescript/typescript6@6.0.2"
}
}

View File

@@ -0,0 +1,100 @@
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',
isNative:
distribution === 'desktop' ||
distribution === 'ios' ||
distribution === 'android',
isAdmin: distribution === 'admin',
appBuildType: 'stable' as const,
appVersion: pkg.version,
// editorVersion: pkg.dependencies['@blocksuite/affine'],
editorVersion: pkg.version,
githubUrl: 'https://github.com/toeverything/AFFiNE',
changelogUrl: 'https://affine.pro/what-is-new',
downloadUrl: 'https://affine.pro/download',
pricingUrl: 'https://affine.pro/pricing',
discordUrl: 'https://affine.pro/redirect/discord',
requestLicenseUrl: 'https://affine.pro/redirect/license',
imageProxyUrl: '/api/worker/image-proxy',
linkPreviewUrl: '/api/worker/link-preview',
SENTRY_DSN: process.env.SENTRY_DSN ?? '',
};
},
get beta() {
return {
...this.stable,
appBuildType: 'beta' as const,
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
};
},
get internal() {
return {
...this.stable,
appBuildType: 'internal' as const,
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
};
},
// canary will be aggressive and enable all features
get canary() {
return {
...this.stable,
appBuildType: 'canary' as const,
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,
};
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),
};
}

View File

@@ -0,0 +1,30 @@
import { PackageList, type PackageName } from './yarn';
export const PackageToDistribution = new Map<
PackageName,
BUILD_CONFIG_TYPE['distribution']
>([
['@affine/admin', 'admin'],
['@affine/web', 'web'],
['@affine/electron-renderer', 'desktop'],
['@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'],
['electron', '@affine/electron'],
['desktop', '@affine/electron-renderer'],
['renderer', '@affine/electron-renderer'],
['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]
),
]);

22
tools/utils/src/format.ts Normal file
View File

@@ -0,0 +1,22 @@
import { readFileSync } from 'node:fs';
import { once } from 'lodash-es';
import { format, type FormatConfig } from 'oxfmt';
import { ProjectRoot } from './path';
const readConfig = once(() => {
const path = ProjectRoot.join('.oxfmtrc.json').value;
const config = JSON.parse(readFileSync(path, 'utf-8')) as FormatConfig;
return config;
});
export async function formatCode(content: string, fileName: string) {
const result = await format(fileName, content, readConfig());
if (result.errors.length) {
throw new Error(
result.errors.map(error => error.codeframe ?? error.message).join('\n')
);
}
return result.code;
}

2
tools/utils/src/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './format';
export * from './path';

46
tools/utils/src/logger.ts Normal file
View File

@@ -0,0 +1,46 @@
import chalk from 'chalk';
import { identity } from 'lodash-es';
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));
}
}
});
});
};
}
}

View File

@@ -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 './yarn';
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);
}
}

93
tools/utils/src/path.ts Normal file
View File

@@ -0,0 +1,93 @@
import {
existsSync,
mkdirSync,
readFileSync,
rmSync,
statSync,
writeFileSync,
} from 'node:fs';
import { join, relative, sep } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
export class Path {
static dir(url: string) {
return new Path(fileURLToPath(url)).join('..');
}
get value() {
return this.path;
}
get relativePath() {
return './' + this.path.slice(ProjectRoot.path.length).replace(/\\/g, '/');
}
constructor(private readonly path: string) {}
join(...paths: string[]) {
return new Path(join(this.path, ...paths));
}
parent() {
return this.join('..');
}
toPosixString() {
if (sep === '\\') {
return this.path.replaceAll('\\', '/');
}
return this.path;
}
toString() {
return this.path;
}
exists() {
return existsSync(this.path);
}
rm(opts: { recursive?: boolean } = {}) {
rmSync(this.path, { ...opts, force: true });
}
mkdir() {
mkdirSync(this.path, { recursive: true });
}
readAsFile() {
return readFileSync(this.path);
}
writeFile(content: Buffer | string) {
writeFileSync(this.path, content);
}
stats() {
return statSync(this.path);
}
isFile() {
return this.exists() && this.stats().isFile();
}
isDirectory() {
return this.exists() && this.stats().isDirectory();
}
toFileUrl() {
return pathToFileURL(this.path);
}
relative(to: string) {
const re = relative(this.value, to);
if (sep === '\\') {
return re.replaceAll('\\', '/');
}
return re;
}
}
export const ProjectRoot = Path.dir(import.meta.url).join('../../../');

View File

@@ -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;
}

19
tools/utils/src/types.ts Normal file
View File

@@ -0,0 +1,19 @@
export interface YarnWorkspaceItem {
name: string;
location: string;
workspaceDependencies: string[];
// we don't need it
mismatchedWorkspaceDependencies?: string[];
}
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 } };
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,152 @@
import { Logger } from './logger';
import { Package, readPackageJson } from './package';
import { ProjectRoot } from './path';
import type { CommonPackageJsonContent } from './types';
import { PackageList, type PackageName, yarnList } from './yarn';
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 ?? {};
}
get isTsProject() {
return this.join('tsconfig.json').exists();
}
constructor(list: typeof PackageList = PackageList) {
this.packageJson = readPackageJson(ProjectRoot);
const packages = new Map<string, Package>();
for (const meta of list) {
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);
console.log(inProcessPackages, e.currentName);
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(dep.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);
}
forEach(callback: (pkg: Package) => void) {
this.packages.forEach(callback);
}
}
export { Package, type PackageName, yarnList };

36
tools/utils/src/yarn.ts Normal file
View File

@@ -0,0 +1,36 @@
import { once } from 'lodash-es';
import { Logger } from './logger';
import { exec } from './process';
import type { YarnWorkspaceItem } from './types';
import type { PackageName } from './workspace.gen';
async function loadPackageList() {
try {
const packageList = await import('./workspace.gen');
return packageList.PackageList;
} catch (e) {
console.log(e);
new Logger('yarn').error('Failed to load package list');
return [];
}
}
export const PackageList = await loadPackageList();
export type { PackageName };
export const 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 !== '.');
});

10
tools/utils/tsconfig.json Normal file
View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.node.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
},
"include": ["./src"],
"references": []
}