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
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:
23
tools/utils/package.json
Normal file
23
tools/utils/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
100
tools/utils/src/build-config.ts
Normal file
100
tools/utils/src/build-config.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
30
tools/utils/src/distribution.ts
Normal file
30
tools/utils/src/distribution.ts
Normal 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
22
tools/utils/src/format.ts
Normal 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
2
tools/utils/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './format';
|
||||
export * from './path';
|
||||
46
tools/utils/src/logger.ts
Normal file
46
tools/utils/src/logger.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
81
tools/utils/src/package.ts
Normal file
81
tools/utils/src/package.ts
Normal 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
93
tools/utils/src/path.ts
Normal 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('../../../');
|
||||
99
tools/utils/src/process.ts
Normal file
99
tools/utils/src/process.ts
Normal 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
19
tools/utils/src/types.ts
Normal 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 } };
|
||||
}
|
||||
1581
tools/utils/src/workspace.gen.ts
Normal file
1581
tools/utils/src/workspace.gen.ts
Normal file
File diff suppressed because it is too large
Load Diff
152
tools/utils/src/workspace.ts
Normal file
152
tools/utils/src/workspace.ts
Normal 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
36
tools/utils/src/yarn.ts
Normal 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
10
tools/utils/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.node.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"include": ["./src"],
|
||||
"references": []
|
||||
}
|
||||
Reference in New Issue
Block a user